Compare commits
4 Commits
756953fbf9
...
5e5792f17c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e5792f17c | ||
|
|
9f40a6c782 | ||
|
|
c095cdab3a | ||
|
|
cea35955e4 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -33,3 +33,6 @@ build/
|
|||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
**/docker/postgresql/pgdata
|
**/docker/postgresql/pgdata
|
||||||
|
|
||||||
|
**/node_modules
|
||||||
|
**/.angular
|
||||||
@@ -29,10 +29,6 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-security</artifactId>
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>jakarta.servlet</groupId>
|
|
||||||
<artifactId>jakarta.servlet-api</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.auth0</groupId>
|
<groupId>com.auth0</groupId>
|
||||||
<artifactId>java-jwt</artifactId>
|
<artifactId>java-jwt</artifactId>
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
package org.sportshub.application.security;
|
package org.sportshub.exposition.configuration.security;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
|
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
|
||||||
import static org.springframework.util.ObjectUtils.isEmpty;
|
import static org.springframework.util.ObjectUtils.isEmpty;
|
||||||
|
import org.sportshub.application.security.JwtService;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
package org.sportshub.exposition.configuration;
|
package org.sportshub.exposition.configuration.security;
|
||||||
|
|
||||||
import static org.springframework.http.HttpMethod.GET;
|
import static org.springframework.http.HttpMethod.GET;
|
||||||
import static org.springframework.http.HttpMethod.OPTIONS;
|
import static org.springframework.http.HttpMethod.OPTIONS;
|
||||||
import static org.springframework.http.HttpMethod.POST;
|
import static org.springframework.http.HttpMethod.POST;
|
||||||
import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;
|
import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;
|
||||||
import org.sportshub.application.security.JwtAuthenticationFilter;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.security.config.Customizer;
|
import org.springframework.security.config.Customizer;
|
||||||
@@ -12,8 +11,6 @@ import org.springframework.security.config.annotation.method.configuration.Enabl
|
|||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
|
||||||
@@ -2,6 +2,8 @@ package org.sportshub.exposition.user;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.sportshub.application.security.annotation.AllowedToAdmins;
|
||||||
|
import org.sportshub.application.security.annotation.AllowedToAnonymous;
|
||||||
import org.sportshub.application.user.UserUseCases;
|
import org.sportshub.application.user.UserUseCases;
|
||||||
import org.sportshub.domain.user.model.User;
|
import org.sportshub.domain.user.model.User;
|
||||||
import org.sportshub.domain.user.model.UserAuthenticationData;
|
import org.sportshub.domain.user.model.UserAuthenticationData;
|
||||||
@@ -24,12 +26,14 @@ public class UserController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
|
@AllowedToAnonymous
|
||||||
public LoginResponse login(@RequestBody LoginRequest request) {
|
public LoginResponse login(@RequestBody LoginRequest request) {
|
||||||
UserAuthenticationData userAuthenticationData = userUseCases.authenticate(request.id(), request.password());
|
UserAuthenticationData userAuthenticationData = userUseCases.authenticate(request.id(), request.password());
|
||||||
return new LoginResponse(userAuthenticationData);
|
return new LoginResponse(userAuthenticationData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
|
@AllowedToAdmins
|
||||||
public List<User> findAll() {
|
public List<User> findAll() {
|
||||||
return userUseCases.findAll();
|
return userUseCases.findAll();
|
||||||
}
|
}
|
||||||
|
|||||||
16
sportshub-gui/.editorconfig
Normal file
16
sportshub-gui/.editorconfig
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Editor configuration, see https://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.ts]
|
||||||
|
quote_type = single
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
max_line_length = off
|
||||||
|
trim_trailing_whitespace = false
|
||||||
102
sportshub-gui/angular.json
Normal file
102
sportshub-gui/angular.json
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"version": 1,
|
||||||
|
"newProjectRoot": "projects",
|
||||||
|
"projects": {
|
||||||
|
"sportshub-gui": {
|
||||||
|
"projectType": "application",
|
||||||
|
"schematics": {
|
||||||
|
"@schematics/angular:component": {
|
||||||
|
"style": "scss"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"prefix": "app",
|
||||||
|
"architect": {
|
||||||
|
"build": {
|
||||||
|
"builder": "@angular-devkit/build-angular:application",
|
||||||
|
"options": {
|
||||||
|
"outputPath": "dist/sportshub-gui",
|
||||||
|
"index": "src/index.html",
|
||||||
|
"browser": "src/main.ts",
|
||||||
|
"polyfills": [
|
||||||
|
"zone.js"
|
||||||
|
],
|
||||||
|
"tsConfig": "tsconfig.app.json",
|
||||||
|
"inlineStyleLanguage": "scss",
|
||||||
|
"assets": [
|
||||||
|
"src/favicon.ico",
|
||||||
|
"src/assets"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"@angular/material/prebuilt-themes/indigo-pink.css",
|
||||||
|
"src/styles.scss"
|
||||||
|
],
|
||||||
|
"scripts": []
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"budgets": [
|
||||||
|
{
|
||||||
|
"type": "initial",
|
||||||
|
"maximumWarning": "500kb",
|
||||||
|
"maximumError": "1mb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "anyComponentStyle",
|
||||||
|
"maximumWarning": "2kb",
|
||||||
|
"maximumError": "4kb"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputHashing": "all"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"optimization": false,
|
||||||
|
"extractLicenses": false,
|
||||||
|
"sourceMap": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "production"
|
||||||
|
},
|
||||||
|
"serve": {
|
||||||
|
"builder": "@angular-devkit/build-angular:dev-server",
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"buildTarget": "sportshub-gui:build:production"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"buildTarget": "sportshub-gui:build:development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "development"
|
||||||
|
},
|
||||||
|
"extract-i18n": {
|
||||||
|
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||||
|
"options": {
|
||||||
|
"buildTarget": "sportshub-gui:build"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"builder": "@angular-devkit/build-angular:jest",
|
||||||
|
"options": {
|
||||||
|
"polyfills": [
|
||||||
|
"zone.js",
|
||||||
|
"zone.js/testing"
|
||||||
|
],
|
||||||
|
"tsConfig": "tsconfig.spec.json",
|
||||||
|
"inlineStyleLanguage": "scss",
|
||||||
|
"assets": [
|
||||||
|
"src/favicon.ico",
|
||||||
|
"src/assets"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"src/styles.scss"
|
||||||
|
],
|
||||||
|
"scripts": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17658
sportshub-gui/package-lock.json
generated
Normal file
17658
sportshub-gui/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
50
sportshub-gui/package.json
Normal file
50
sportshub-gui/package.json
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"name": "sportshub-gui",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"ng": "ng",
|
||||||
|
"start": "ng serve --proxy-config proxy.conf.json",
|
||||||
|
"build": "ng build",
|
||||||
|
"watch": "ng build --watch --configuration development",
|
||||||
|
"test": "ng test"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@angular/animations": "^17.0.0",
|
||||||
|
"@angular/cdk": "^17.0.2",
|
||||||
|
"@angular/common": "^17.0.0",
|
||||||
|
"@angular/compiler": "^17.0.0",
|
||||||
|
"@angular/core": "^17.0.0",
|
||||||
|
"@angular/forms": "^17.0.0",
|
||||||
|
"@angular/material": "^17.0.2",
|
||||||
|
"@angular/platform-browser": "^17.0.0",
|
||||||
|
"@angular/platform-browser-dynamic": "^17.0.0",
|
||||||
|
"@angular/router": "^17.0.0",
|
||||||
|
"ngx-cookie-service": "^17.0.0",
|
||||||
|
"rxjs": "~7.8.0",
|
||||||
|
"tslib": "^2.3.0",
|
||||||
|
"zone.js": "~0.14.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@angular-devkit/build-angular": "^17.0.5",
|
||||||
|
"@angular/cli": "^17.0.5",
|
||||||
|
"@angular/compiler-cli": "^17.0.0",
|
||||||
|
"@types/jasmine": "~5.1.0",
|
||||||
|
"@types/jest": "^29.5.10",
|
||||||
|
"jasmine-core": "~5.1.0",
|
||||||
|
"jest": "^29.7.0",
|
||||||
|
"jest-preset-angular": "^13.1.4",
|
||||||
|
"karma": "~6.4.0",
|
||||||
|
"karma-chrome-launcher": "~3.2.0",
|
||||||
|
"karma-coverage": "~2.2.0",
|
||||||
|
"karma-jasmine": "~5.1.0",
|
||||||
|
"karma-jasmine-html-reporter": "~2.1.0",
|
||||||
|
"typescript": "~5.2.2"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"preset": "jest-preset-angular",
|
||||||
|
"setupFilesAfterEnv": [
|
||||||
|
"<rootDir>/src/setupJest.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
6
sportshub-gui/proxy.conf.json
Normal file
6
sportshub-gui/proxy.conf.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"/api": {
|
||||||
|
"target": "http://localhost:8080",
|
||||||
|
"secure": false
|
||||||
|
}
|
||||||
|
}
|
||||||
2
sportshub-gui/src/app/app.component.html
Normal file
2
sportshub-gui/src/app/app.component.html
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<app-header/>
|
||||||
|
<router-outlet></router-outlet>
|
||||||
8
sportshub-gui/src/app/app.component.scss
Normal file
8
sportshub-gui/src/app/app.component.scss
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
app-header {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
sportshub-gui/src/app/app.component.ts
Normal file
21
sportshub-gui/src/app/app.component.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { RouterOutlet } from '@angular/router';
|
||||||
|
import {HttpClientModule} from "@angular/common/http";
|
||||||
|
import {LoginModule} from "./components/login/login.module";
|
||||||
|
import {HeaderComponent} from "./header/header.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
RouterOutlet,
|
||||||
|
HeaderComponent,
|
||||||
|
],
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrl: './app.component.scss'
|
||||||
|
})
|
||||||
|
export class AppComponent {
|
||||||
|
title = 'sportshub-gui';
|
||||||
|
}
|
||||||
14
sportshub-gui/src/app/app.config.ts
Normal file
14
sportshub-gui/src/app/app.config.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { ApplicationConfig } from '@angular/core';
|
||||||
|
import { provideRouter } from '@angular/router';
|
||||||
|
|
||||||
|
import { routes } from './app.routes';
|
||||||
|
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||||
|
import {provideHttpClient} from "@angular/common/http";
|
||||||
|
|
||||||
|
export const appConfig: ApplicationConfig = {
|
||||||
|
providers: [
|
||||||
|
provideRouter(routes),
|
||||||
|
provideHttpClient(),
|
||||||
|
provideAnimations()
|
||||||
|
]
|
||||||
|
};
|
||||||
16
sportshub-gui/src/app/app.routes.ts
Normal file
16
sportshub-gui/src/app/app.routes.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Routes } from '@angular/router';
|
||||||
|
|
||||||
|
export const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
loadChildren: () => import('./components/home/home.module').then(module => module.HomeModule)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'login',
|
||||||
|
loadChildren: () => import('./components/login/login.module').then(module => module.LoginModule)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'logout',
|
||||||
|
loadChildren: () => import('./components/logout/logout.module').then(module => module.LogoutModule)
|
||||||
|
}
|
||||||
|
];
|
||||||
17
sportshub-gui/src/app/app.service.spec.ts
Normal file
17
sportshub-gui/src/app/app.service.spec.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import {AppService} from "./app.service";
|
||||||
|
|
||||||
|
describe('In the service AppService', () => {
|
||||||
|
let service: AppService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
service = new AppService();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('The method "test"', () => {
|
||||||
|
it('should return "true"', () => {
|
||||||
|
const result = service.test();
|
||||||
|
|
||||||
|
expect(result).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
8
sportshub-gui/src/app/app.service.ts
Normal file
8
sportshub-gui/src/app/app.service.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import {Injectable} from "@angular/core";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AppService {
|
||||||
|
test(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<h1>Hello world!</h1>
|
||||||
10
sportshub-gui/src/app/components/home/home.component.ts
Normal file
10
sportshub-gui/src/app/components/home/home.component.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import {Component} from "@angular/core";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-home',
|
||||||
|
templateUrl: './home.component.html',
|
||||||
|
styleUrls: ['./home.component.scss']
|
||||||
|
})
|
||||||
|
export class HomeComponent {
|
||||||
|
|
||||||
|
}
|
||||||
25
sportshub-gui/src/app/components/home/home.module.ts
Normal file
25
sportshub-gui/src/app/components/home/home.module.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import {NgModule} from "@angular/core";
|
||||||
|
import {HomeComponent} from "./home.component";
|
||||||
|
import {RouterModule} from "@angular/router";
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: HomeComponent
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
HomeComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild(routes),
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
HomeComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class HomeModule {
|
||||||
|
|
||||||
|
}
|
||||||
13
sportshub-gui/src/app/components/login/login.component.html
Normal file
13
sportshub-gui/src/app/components/login/login.component.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<form (ngSubmit)="onSubmit()" class="shadowed" [formGroup]="loginForm" ngNativeValidate>
|
||||||
|
<div>
|
||||||
|
<label for="id">Identifier</label>
|
||||||
|
<input id="id" name="id" formControlName="id" class="input" required/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input id="password" name="password" type="password" formControlName="password" class="input" required/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button type="submit" class="btn">Validate</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
19
sportshub-gui/src/app/components/login/login.component.scss
Normal file
19
sportshub-gui/src/app/components/login/login.component.scss
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border: solid 1px #e8e8e8;
|
||||||
|
padding: 1em;
|
||||||
|
border-radius: .5em;
|
||||||
|
gap: 1em;
|
||||||
|
|
||||||
|
div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
sportshub-gui/src/app/components/login/login.component.ts
Normal file
35
sportshub-gui/src/app/components/login/login.component.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import {Component, OnInit} from "@angular/core";
|
||||||
|
import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms";
|
||||||
|
import {UserRestService} from "../../core/rest-services/user.rest-service";
|
||||||
|
import {LoginRequest} from "../../core/model/login-request";
|
||||||
|
import {MatSnackBar} from "@angular/material/snack-bar";
|
||||||
|
import {LoginService} from "./login.service";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-login',
|
||||||
|
templateUrl: './login.component.html',
|
||||||
|
styleUrls: ['./login.component.scss']
|
||||||
|
})
|
||||||
|
export class LoginComponent {
|
||||||
|
loginForm: FormGroup;
|
||||||
|
isLoginPending: boolean = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private loginService: LoginService,
|
||||||
|
private matSnackBar: MatSnackBar,
|
||||||
|
private userRestService: UserRestService
|
||||||
|
) {
|
||||||
|
this.loginForm = this.formBuilder.group({
|
||||||
|
id: new FormControl(undefined, [Validators.required]),
|
||||||
|
password: new FormControl(undefined, [Validators.required])
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit(): void {
|
||||||
|
if (this.loginForm.valid) {
|
||||||
|
const loginRequest: LoginRequest = this.loginForm.value;
|
||||||
|
this.loginService.login(loginRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
sportshub-gui/src/app/components/login/login.module.ts
Normal file
34
sportshub-gui/src/app/components/login/login.module.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import {NgModule} from "@angular/core";
|
||||||
|
import {LoginComponent} from "./login.component";
|
||||||
|
import {CoreModule} from "../../core/core.module";
|
||||||
|
import {MatSnackBarModule} from "@angular/material/snack-bar";
|
||||||
|
import {RouterModule} from "@angular/router";
|
||||||
|
import {HttpClientModule} from "@angular/common/http";
|
||||||
|
import {LoginService} from "./login.service";
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: LoginComponent
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
LoginComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
LoginService
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CoreModule,
|
||||||
|
RouterModule.forChild(routes),
|
||||||
|
MatSnackBarModule
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
LoginComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class LoginModule {
|
||||||
|
|
||||||
|
}
|
||||||
37
sportshub-gui/src/app/components/login/login.service.ts
Normal file
37
sportshub-gui/src/app/components/login/login.service.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import {Injectable} from "@angular/core";
|
||||||
|
import {UserRestService} from "../../core/rest-services/user.rest-service";
|
||||||
|
import {LoginRequest} from "../../core/model/login-request";
|
||||||
|
import {Subject} from "rxjs";
|
||||||
|
import {MessageService} from "../../core/services/message.service";
|
||||||
|
import {AuthenticationService} from "../../core/services/authentication.service";
|
||||||
|
import {Router} from "@angular/router";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LoginService {
|
||||||
|
private isLoginPending: Subject<boolean> = new Subject<boolean>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private authenticationService: AuthenticationService,
|
||||||
|
private messageService: MessageService,
|
||||||
|
private router: Router,
|
||||||
|
private userRestService: UserRestService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
login(loginRequest: LoginRequest): void {
|
||||||
|
this.isLoginPending.next(true);
|
||||||
|
|
||||||
|
this.userRestService.login(loginRequest)
|
||||||
|
.then(loginResponse => {
|
||||||
|
this.messageService.display('Login success!');
|
||||||
|
this.authenticationService.setAuthenticated(loginResponse);
|
||||||
|
this.router.navigate(['/']);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
if (error.status === 400) {
|
||||||
|
this.messageService.display('Login or password incorrect.')
|
||||||
|
} else {
|
||||||
|
this.messageService.display('An error occured while login.')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<h1>Disconnection...</h1>
|
||||||
|
<mat-spinner></mat-spinner>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
15
sportshub-gui/src/app/components/logout/logout.component.ts
Normal file
15
sportshub-gui/src/app/components/logout/logout.component.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import {Component, inject, OnInit} from "@angular/core";
|
||||||
|
import {LogoutService} from "./logout.service";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-logout',
|
||||||
|
templateUrl: './logout.component.html',
|
||||||
|
styleUrls: ['./logout.component.scss']
|
||||||
|
})
|
||||||
|
export class LogoutComponent implements OnInit {
|
||||||
|
private logoutService = inject(LogoutService);
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.logoutService.logout();
|
||||||
|
}
|
||||||
|
}
|
||||||
29
sportshub-gui/src/app/components/logout/logout.module.ts
Normal file
29
sportshub-gui/src/app/components/logout/logout.module.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import {NgModule} from "@angular/core";
|
||||||
|
import {RouterModule} from "@angular/router";
|
||||||
|
import {LogoutComponent} from "./logout.component";
|
||||||
|
import {MatProgressSpinnerModule} from "@angular/material/progress-spinner";
|
||||||
|
import {LogoutService} from "./logout.service";
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: LogoutComponent
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
LogoutComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
LogoutService
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild(routes),
|
||||||
|
MatProgressSpinnerModule
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
LogoutComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class LogoutModule {}
|
||||||
16
sportshub-gui/src/app/components/logout/logout.service.ts
Normal file
16
sportshub-gui/src/app/components/logout/logout.service.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import {Injectable} from "@angular/core";
|
||||||
|
import {AuthenticationService} from "../../core/services/authentication.service";
|
||||||
|
import {Router} from "@angular/router";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LogoutService {
|
||||||
|
constructor(
|
||||||
|
private authenticationService: AuthenticationService,
|
||||||
|
private router: Router
|
||||||
|
) {}
|
||||||
|
|
||||||
|
logout(): void {
|
||||||
|
this.authenticationService.setAnonymous();
|
||||||
|
this.router.navigate(['/']);
|
||||||
|
}
|
||||||
|
}
|
||||||
17
sportshub-gui/src/app/core/core.module.ts
Normal file
17
sportshub-gui/src/app/core/core.module.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import {NgModule} from "@angular/core";
|
||||||
|
import {ReactiveFormsModule} from "@angular/forms";
|
||||||
|
import {HttpClientModule} from "@angular/common/http";
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
ReactiveFormsModule,
|
||||||
|
HttpClientModule,
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
ReactiveFormsModule,
|
||||||
|
HttpClientModule
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class CoreModule {
|
||||||
|
|
||||||
|
}
|
||||||
4
sportshub-gui/src/app/core/model/login-request.ts
Normal file
4
sportshub-gui/src/app/core/model/login-request.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export interface LoginRequest {
|
||||||
|
id: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
5
sportshub-gui/src/app/core/model/login-response.ts
Normal file
5
sportshub-gui/src/app/core/model/login-response.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export interface LoginResponse {
|
||||||
|
tokenType: string;
|
||||||
|
accessToken: string;
|
||||||
|
refreshToken: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import {Injectable} from "@angular/core";
|
||||||
|
import {HttpClient} from "@angular/common/http";
|
||||||
|
import {LoginResponse} from "../model/login-response";
|
||||||
|
import {firstValueFrom} from "rxjs";
|
||||||
|
import {LoginRequest} from "../model/login-request";
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class UserRestService {
|
||||||
|
constructor(
|
||||||
|
private httpClient: HttpClient
|
||||||
|
) {}
|
||||||
|
|
||||||
|
login(request: LoginRequest): Promise<LoginResponse> {
|
||||||
|
return firstValueFrom(
|
||||||
|
this.httpClient.post<LoginResponse>('/api/users/login', request)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import {Injectable} from "@angular/core";
|
||||||
|
import {CookieService} from "ngx-cookie-service";
|
||||||
|
import {LoginResponse} from "../model/login-response";
|
||||||
|
import {BehaviorSubject, Observable} from "rxjs";
|
||||||
|
|
||||||
|
const COOKIE_JWT = 'jwt';
|
||||||
|
const COOKIE_REFRESH_TOKEN = 'refreshToken';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class AuthenticationService {
|
||||||
|
private authenticationSubject: BehaviorSubject<boolean>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private cookieService: CookieService
|
||||||
|
) {
|
||||||
|
const isAuthenticated = this.isAuthenticated();
|
||||||
|
this.authenticationSubject = new BehaviorSubject<boolean>(isAuthenticated);
|
||||||
|
}
|
||||||
|
|
||||||
|
get isAuthenticated$(): Observable<boolean> {
|
||||||
|
return this.authenticationSubject.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
setAuthenticated(loginResponse: LoginResponse): void {
|
||||||
|
const jwt = loginResponse.accessToken;
|
||||||
|
this.cookieService.set(COOKIE_JWT, jwt);
|
||||||
|
|
||||||
|
const refreshToken = loginResponse.refreshToken;
|
||||||
|
this.cookieService.set(COOKIE_REFRESH_TOKEN, refreshToken);
|
||||||
|
this.authenticationSubject.next(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
setAnonymous(): void {
|
||||||
|
this.cookieService.delete(COOKIE_JWT);
|
||||||
|
this.cookieService.delete(COOKIE_REFRESH_TOKEN);
|
||||||
|
this.authenticationSubject.next(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
isAuthenticated(): boolean {
|
||||||
|
const jwt = this.cookieService.get(COOKIE_JWT);
|
||||||
|
return jwt?.length > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
15
sportshub-gui/src/app/core/services/message.service.ts
Normal file
15
sportshub-gui/src/app/core/services/message.service.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import {inject, Injectable} from "@angular/core";
|
||||||
|
import {MatSnackBar} from "@angular/material/snack-bar";
|
||||||
|
|
||||||
|
const MESSAGE_DURATION = 5000;
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class MessageService {
|
||||||
|
private matSnackBar = inject(MatSnackBar);
|
||||||
|
|
||||||
|
display(message: string): void {
|
||||||
|
this.matSnackBar.open(message, 'Close', { duration: MESSAGE_DURATION });
|
||||||
|
}
|
||||||
|
}
|
||||||
11
sportshub-gui/src/app/header/header.component.html
Normal file
11
sportshub-gui/src/app/header/header.component.html
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<a class="title" routerLink="/">SportsHub</a>
|
||||||
|
<div id="menu">
|
||||||
|
<a routerLink="/login" *ngIf="(isAuthenticated$ | async) === false" class="btn">
|
||||||
|
<mat-icon>login</mat-icon>
|
||||||
|
Login
|
||||||
|
</a>
|
||||||
|
<a routerLink="/logout" *ngIf="isAuthenticated$ | async" class="btn logout">
|
||||||
|
<mat-icon>logout</mat-icon>
|
||||||
|
Logout
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
34
sportshub-gui/src/app/header/header.component.scss
Normal file
34
sportshub-gui/src/app/header/header.component.scss
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: .5em 1em;
|
||||||
|
background-color: #004680;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 2em;
|
||||||
|
text-decoration: none;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: .5em;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 1em;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
a {
|
||||||
|
flex: 0 1;
|
||||||
|
gap: .5em;
|
||||||
|
|
||||||
|
&.logout {
|
||||||
|
background-color: #c20000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
sportshub-gui/src/app/header/header.component.ts
Normal file
28
sportshub-gui/src/app/header/header.component.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import {Component} from "@angular/core";
|
||||||
|
import {RouterLink} from "@angular/router";
|
||||||
|
import {AuthenticationService} from "../core/services/authentication.service";
|
||||||
|
import {Observable} from "rxjs";
|
||||||
|
import {AsyncPipe, NgIf} from "@angular/common";
|
||||||
|
import {MatIconModule} from "@angular/material/icon";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-header',
|
||||||
|
standalone: true,
|
||||||
|
templateUrl: './header.component.html',
|
||||||
|
imports: [
|
||||||
|
RouterLink,
|
||||||
|
AsyncPipe,
|
||||||
|
NgIf,
|
||||||
|
MatIconModule
|
||||||
|
],
|
||||||
|
styleUrls: ['./header.component.scss']
|
||||||
|
})
|
||||||
|
export class HeaderComponent {
|
||||||
|
isAuthenticated$: Observable<boolean>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private authenticationService: AuthenticationService
|
||||||
|
) {
|
||||||
|
this.isAuthenticated$ = this.authenticationService.isAuthenticated$;
|
||||||
|
}
|
||||||
|
}
|
||||||
0
sportshub-gui/src/assets/.gitkeep
Normal file
0
sportshub-gui/src/assets/.gitkeep
Normal file
BIN
sportshub-gui/src/favicon.ico
Normal file
BIN
sportshub-gui/src/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
15
sportshub-gui/src/index.html
Normal file
15
sportshub-gui/src/index.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>SportshubGui</title>
|
||||||
|
<base href="/">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
|
||||||
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body class="mat-typography">
|
||||||
|
<app-root></app-root>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
6
sportshub-gui/src/main.ts
Normal file
6
sportshub-gui/src/main.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { bootstrapApplication } from '@angular/platform-browser';
|
||||||
|
import { appConfig } from './app/app.config';
|
||||||
|
import { AppComponent } from './app/app.component';
|
||||||
|
|
||||||
|
bootstrapApplication(AppComponent, appConfig)
|
||||||
|
.catch((err) => console.error(err));
|
||||||
0
sportshub-gui/src/setupJest.ts
Normal file
0
sportshub-gui/src/setupJest.ts
Normal file
26
sportshub-gui/src/styles.scss
Normal file
26
sportshub-gui/src/styles.scss
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/* You can add global styles to this file, and also import other style files */
|
||||||
|
|
||||||
|
html, body { height: 100%; }
|
||||||
|
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
|
||||||
|
|
||||||
|
.shadowed {
|
||||||
|
box-shadow: 0 3px 1px -2px #0003,0 2px 2px #00000024,0 1px 5px #0000001f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
background-color: #008cff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: .4em;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: .5em 1em;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
padding: .5em;
|
||||||
|
border-radius: .4em;
|
||||||
|
border: 1px solid #d2d2d2
|
||||||
|
}
|
||||||
14
sportshub-gui/tsconfig.app.json
Normal file
14
sportshub-gui/tsconfig.app.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./out-tsc/app",
|
||||||
|
"types": []
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"src/main.ts"
|
||||||
|
],
|
||||||
|
"include": [
|
||||||
|
"src/**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
32
sportshub-gui/tsconfig.json
Normal file
32
sportshub-gui/tsconfig.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
|
{
|
||||||
|
"compileOnSave": false,
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist/out-tsc",
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"declaration": false,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"importHelpers": true,
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ES2022",
|
||||||
|
"useDefineForClassFields": false,
|
||||||
|
"lib": [
|
||||||
|
"ES2022",
|
||||||
|
"dom"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"enableI18nLegacyMessageIdFormat": false,
|
||||||
|
"strictInjectionParameters": true,
|
||||||
|
"strictInputAccessModifiers": true,
|
||||||
|
"strictTemplates": true
|
||||||
|
}
|
||||||
|
}
|
||||||
14
sportshub-gui/tsconfig.spec.json
Normal file
14
sportshub-gui/tsconfig.spec.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./out-tsc/spec",
|
||||||
|
"types": [
|
||||||
|
"jasmine"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.spec.ts",
|
||||||
|
"src/**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user