Merge pull request #11 from liosha84/10-add-angular-material-project
add angular material project
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
# 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
|
||||
ij_typescript_use_double_quotes = false
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
||||
@@ -0,0 +1,42 @@
|
||||
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
|
||||
|
||||
# Compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
/bazel-out
|
||||
|
||||
# Node
|
||||
/node_modules
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# IDEs and editors
|
||||
.idea/
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# Miscellaneous
|
||||
/.angular/cache
|
||||
.sass-cache/
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -0,0 +1,59 @@
|
||||
# JambotronUi
|
||||
|
||||
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 20.0.2.
|
||||
|
||||
## Development server
|
||||
|
||||
To start a local development server, run:
|
||||
|
||||
```bash
|
||||
ng serve
|
||||
```
|
||||
|
||||
Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
|
||||
|
||||
```bash
|
||||
ng generate component component-name
|
||||
```
|
||||
|
||||
For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
|
||||
|
||||
```bash
|
||||
ng generate --help
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To build the project run:
|
||||
|
||||
```bash
|
||||
ng build
|
||||
```
|
||||
|
||||
This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
|
||||
|
||||
```bash
|
||||
ng test
|
||||
```
|
||||
|
||||
## Running end-to-end tests
|
||||
|
||||
For end-to-end (e2e) testing, run:
|
||||
|
||||
```bash
|
||||
ng e2e
|
||||
```
|
||||
|
||||
Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
|
||||
|
||||
## Additional Resources
|
||||
|
||||
For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
|
||||
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"jambotron-ui": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"style": "scss"
|
||||
}
|
||||
},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular/build:application",
|
||||
"options": {
|
||||
"browser": "src/main.ts",
|
||||
"polyfills": [
|
||||
"zone.js"
|
||||
],
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "public"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"@angular/material/prebuilt-themes/cyan-orange.css",
|
||||
"src/styles.scss"
|
||||
]
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kB",
|
||||
"maximumError": "1MB"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "4kB",
|
||||
"maximumError": "8kB"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"development": {
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular/build:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "jambotron-ui:build:production"
|
||||
},
|
||||
"development": {
|
||||
"buildTarget": "jambotron-ui:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular/build:extract-i18n"
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular/build:karma",
|
||||
"options": {
|
||||
"polyfills": [
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
],
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "public"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"@angular/material/prebuilt-themes/cyan-orange.css",
|
||||
"src/styles.scss"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+9501
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "jambotron-ui",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test",
|
||||
"serve:ssr:jambotron-ui": "node dist/jambotron-ui/server/server.mjs"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/cdk": "^20.0.3",
|
||||
"@angular/common": "^20.0.0",
|
||||
"@angular/compiler": "^20.0.0",
|
||||
"@angular/core": "^20.0.0",
|
||||
"@angular/forms": "^20.0.0",
|
||||
"@angular/material": "^20.0.3",
|
||||
"@angular/platform-browser": "^20.0.0",
|
||||
"@angular/platform-browser-dynamic": "^20.0.3",
|
||||
"@angular/platform-server": "^20.0.0",
|
||||
"@angular/router": "^20.0.0",
|
||||
"@angular/ssr": "^20.0.2",
|
||||
"express": "^5.1.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/build": "^20.0.2",
|
||||
"@angular/cli": "^20.0.2",
|
||||
"@angular/compiler-cli": "^20.0.0",
|
||||
"@types/express": "^5.0.1",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^20.17.19",
|
||||
"jasmine-core": "~5.7.0",
|
||||
"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.8.2"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { EventBusService } from './event-bus.service';
|
||||
|
||||
describe('EventBusService', () => {
|
||||
let service: EventBusService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(EventBusService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Subject, Subscription } from 'rxjs';
|
||||
import { filter, map } from 'rxjs/operators';
|
||||
import { EventData } from './event.class';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class EventBusService {
|
||||
private subject$ = new Subject<EventData>();
|
||||
|
||||
emit(event: EventData) {
|
||||
this.subject$.next(event);
|
||||
}
|
||||
|
||||
on(eventName: string, action: any): Subscription {
|
||||
return this.subject$.pipe(
|
||||
filter((e: EventData) => e.name === eventName),
|
||||
map((e: EventData) => e["value"])).subscribe(action);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export class EventData {
|
||||
name: string;
|
||||
value: any;
|
||||
|
||||
constructor(name: string, value: any) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { TutorialsListComponent } from './components/tutorials-list/tutorials-list.component';
|
||||
import { TutorialDetailsComponent } from './components/tutorial-details/tutorial-details.component';
|
||||
import { AddTutorialComponent } from './components/add-tutorial/add-tutorial.component';
|
||||
import { BoardAdminComponent } from './components/board-admin/board-admin.component';
|
||||
import { BoardModeratorComponent } from './components/board-moderator/board-moderator.component';
|
||||
import { BoardUserComponent } from './components/board-user/board-user.component';
|
||||
import { ProfileComponent } from './components/profile/profile.component';
|
||||
import { RegisterComponent } from './components/register/register.component';
|
||||
import { LoginComponent } from './components/login/login.component';
|
||||
import { HomeComponent } from './components/home/home.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: 'home', component: HomeComponent },
|
||||
{ path: 'login', component: LoginComponent },
|
||||
{ path: 'register', component: RegisterComponent },
|
||||
{ path: 'profile', component: ProfileComponent },
|
||||
{ path: 'user', component: BoardUserComponent },
|
||||
{ path: 'mod', component: BoardModeratorComponent },
|
||||
{ path: 'admin', component: BoardAdminComponent },
|
||||
{ path: '', redirectTo: 'home', pathMatch: 'full' },
|
||||
{ path: 'tutorials', component: TutorialsListComponent },
|
||||
{ path: 'tutorials/:id', component: TutorialDetailsComponent },
|
||||
{ path: 'add', component: AddTutorialComponent }
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes, {useHash: true})],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AppRoutingModule { }
|
||||
@@ -0,0 +1,79 @@
|
||||
<div>
|
||||
<nav class="navbar navbar-expand navbar-dark bg-dark">
|
||||
<a href="#" class="navbar-brand">Spring-AngularTS</a>
|
||||
|
||||
<ul class="navbar-nav mr-auto" routerLinkActive="active">
|
||||
|
||||
<li class="nav-item">
|
||||
<a routerLink="tutorials" class="nav-link">Tutorials</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a routerLink="add" class="nav-link">Add</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item" *ngIf="showAdminBoard">
|
||||
<a href="/admin" class="nav-link" routerLink="admin">Admin Board</a>
|
||||
</li>
|
||||
<li class="nav-item" *ngIf="showModeratorBoard">
|
||||
<a href="/mod" class="nav-link" routerLink="mod">Moderator Board</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/user" class="nav-link" *ngIf="isLoggedIn" routerLink="user">User</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<ul class="navbar-nav ml-auto" *ngIf="!isLoggedIn">
|
||||
<li class="nav-item">
|
||||
<a href="/register" class="nav-link" routerLink="register">Sign Up</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/login" class="nav-link" routerLink="login">Login</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="navbar-nav ml-auto" *ngIf="isLoggedIn">
|
||||
<li class="nav-item">
|
||||
<a href="/profile" class="nav-link" routerLink="profile">{{ username }}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href class="nav-link" (click)="logout()">LogOut</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</nav>
|
||||
<mat-toolbar>
|
||||
|
||||
<button mat-raised-button routerLink="/" href="#">
|
||||
My App
|
||||
</button>
|
||||
<span>My App
|
||||
</span>
|
||||
<span class="example-spacer"></span>
|
||||
<button mat-raised-button *ngIf="!isLoggedIn">
|
||||
<mat-icon>app_registration</mat-icon>
|
||||
Register
|
||||
</button>
|
||||
|
||||
<button mat-raised-button (click)="openDialog('100ms', '5ms')" *ngIf="!isLoggedIn">
|
||||
<mat-icon>login</mat-icon>
|
||||
Login
|
||||
</button>
|
||||
|
||||
<button mat-raised-button *ngIf="isLoggedIn">
|
||||
<mat-icon>account_circle</mat-icon>
|
||||
{{ username }}
|
||||
</button>
|
||||
|
||||
<button mat-raised-button (click)="logout()"*ngIf="isLoggedIn">
|
||||
<mat-icon>logout</mat-icon>
|
||||
Logout
|
||||
</button>
|
||||
|
||||
</mat-toolbar>
|
||||
|
||||
|
||||
<div class="container mt-3">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,6 @@
|
||||
//#header_panel{
|
||||
// background-color: #181d1f;
|
||||
//}
|
||||
.example-spacer {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
RouterTestingModule
|
||||
],
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
// it(`should have as title 'spring-angular-ui'`, () => {
|
||||
// const fixture = TestBed.createComponent(AppComponent);
|
||||
// const app = fixture.componentInstance;
|
||||
// expect(app.title).toEqual('spring-angular-ui');
|
||||
// });
|
||||
|
||||
it('should render title', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement;
|
||||
expect(compiled.querySelector('.content span').textContent).toContain('spring-angular-ui app is running!');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,113 @@
|
||||
import {Component, inject, OnInit} from '@angular/core';
|
||||
import { TokenStorageService } from './services/token-storage.service';
|
||||
import {MatDialog} from "@angular/material/dialog";
|
||||
import {DialogComponent} from "./components/dialog/dialog.component";
|
||||
import {AuthService} from "./services/auth.service";
|
||||
import {Subscription} from "rxjs";
|
||||
import {EventBusService} from "./_shared/event-bus.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
readonly dialog = inject(MatDialog);
|
||||
//private authService = new AuthService(provideHttpClient())
|
||||
public dialogData : DialogData = {password: "", username: ""};
|
||||
|
||||
private roles: string[] = [];
|
||||
isLoggedIn = false;
|
||||
showAdminBoard = false;
|
||||
showModeratorBoard = false;
|
||||
username?: string;
|
||||
|
||||
eventBusSub?: Subscription;
|
||||
private errorMessage: any;
|
||||
private isLoginFailed: boolean = false;
|
||||
|
||||
constructor(
|
||||
private storageService: TokenStorageService,
|
||||
private authService: AuthService,
|
||||
private eventBusService: EventBusService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.isLoggedIn = this.storageService.isLoggedIn();
|
||||
|
||||
if (this.isLoggedIn) {
|
||||
const user = this.storageService.getUser();
|
||||
this.roles = user.roles;
|
||||
|
||||
this.showAdminBoard = true;//this.roles.includes('ROLE_ADMIN');
|
||||
this.showModeratorBoard = this.roles.includes('ROLE_MODERATOR');
|
||||
|
||||
this.username = user.username;
|
||||
}
|
||||
|
||||
this.eventBusSub = this.eventBusService.on('logout', () => {
|
||||
this.logout();
|
||||
});
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
this.authService.logout().subscribe({
|
||||
next: res => {
|
||||
console.log(res);
|
||||
this.storageService.clean();
|
||||
|
||||
window.location.reload();
|
||||
},
|
||||
error: err => {
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
openDialog(enterAnimationDuration: string, exitAnimationDuration: string) {
|
||||
|
||||
let dialogRef = this.dialog.open(DialogComponent, {
|
||||
width: '350px',
|
||||
enterAnimationDuration,
|
||||
exitAnimationDuration,
|
||||
data: {username: this.dialogData.username, password: this.dialogData.password}
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
console.log('The dialog was closed');
|
||||
if(result== null){
|
||||
return;
|
||||
}
|
||||
this.dialogData = result;
|
||||
|
||||
this.authService.login(this.dialogData.username, this.dialogData.password).subscribe(
|
||||
data => {
|
||||
this.storageService.saveToken(data.accessToken);
|
||||
this.storageService.saveUser(data);
|
||||
|
||||
this.isLoginFailed = false;
|
||||
this.isLoggedIn = true;
|
||||
let user = this.storageService.getUser();
|
||||
this.roles = user.roles;
|
||||
//this.username = user.username;
|
||||
this.reloadPage();
|
||||
},
|
||||
err => {
|
||||
this.errorMessage = err.error.message;
|
||||
this.isLoginFailed = true;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
}
|
||||
reloadPage(): void {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
}
|
||||
export interface DialogData {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { TutorialsListComponent } from './components/tutorials-list/tutorials-list.component';
|
||||
import { TutorialDetailsComponent } from './components/tutorial-details/tutorial-details.component';
|
||||
import { AddTutorialComponent } from './components/add-tutorial/add-tutorial.component';
|
||||
import { LoginComponent } from './components/login/login.component';
|
||||
import { RegisterComponent } from './components/register/register.component';
|
||||
import { HomeComponent } from './components/home/home.component';
|
||||
import { ProfileComponent } from './components/profile/profile.component';
|
||||
import { BoardAdminComponent } from './components/board-admin/board-admin.component';
|
||||
import { BoardModeratorComponent } from './components/board-moderator/board-moderator.component';
|
||||
import { BoardUserComponent } from './components/board-user/board-user.component';
|
||||
|
||||
import { authInterceptorProviders } from './helpers/auth.interceptor';
|
||||
import {provideHttpClient, withInterceptorsFromDi} from "@angular/common/http";
|
||||
import {provideAnimationsAsync} from "@angular/platform-browser/animations/async";
|
||||
import {MatToolbar} from "@angular/material/toolbar";
|
||||
import {MatAnchor, MatButton} from "@angular/material/button";
|
||||
import {MatIcon} from "@angular/material/icon";
|
||||
import {MatChipGrid, MatChipInput, MatChipListbox, MatChipOption, MatChipRow} from "@angular/material/chips";
|
||||
import {MatAutocomplete, MatAutocompleteTrigger, MatOption} from "@angular/material/autocomplete";
|
||||
import {MatFormField} from "@angular/material/form-field";
|
||||
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
TutorialsListComponent,
|
||||
TutorialDetailsComponent,
|
||||
AddTutorialComponent,
|
||||
|
||||
RegisterComponent,
|
||||
HomeComponent,
|
||||
ProfileComponent,
|
||||
BoardAdminComponent,
|
||||
BoardModeratorComponent,
|
||||
BoardUserComponent
|
||||
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
AppRoutingModule,
|
||||
FormsModule,
|
||||
LoginComponent,
|
||||
MatToolbar,
|
||||
MatButton,
|
||||
MatIcon,
|
||||
MatAnchor,
|
||||
MatChipGrid,
|
||||
MatChipRow,
|
||||
MatChipInput,
|
||||
MatAutocompleteTrigger,
|
||||
MatAutocomplete,
|
||||
MatOption,
|
||||
MatFormField,
|
||||
MatChipListbox,
|
||||
MatChipOption,
|
||||
ReactiveFormsModule
|
||||
],
|
||||
providers: [authInterceptorProviders, provideHttpClient(withInterceptorsFromDi()), provideAnimationsAsync()] })
|
||||
|
||||
|
||||
export class AppModule { }
|
||||
@@ -0,0 +1,35 @@
|
||||
<div>
|
||||
<div class="submit-form">
|
||||
<div *ngIf="!submitted">
|
||||
<div class="form-group">
|
||||
<label for="title">Title</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="title"
|
||||
required
|
||||
[(ngModel)]="tutorial.title"
|
||||
name="title"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="description">Description</label>
|
||||
<input
|
||||
class="form-control"
|
||||
id="description"
|
||||
required
|
||||
[(ngModel)]="tutorial.description"
|
||||
name="description"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button (click)="saveTutorial()" class="btn btn-success">Submit</button>
|
||||
</div>
|
||||
|
||||
<div *ngIf="submitted">
|
||||
<h4>Tutorial was submitted successfully!</h4>
|
||||
<button class="btn btn-success" (click)="newTutorial()">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,4 @@
|
||||
.submit-form {
|
||||
max-width: 400px;
|
||||
margin: auto;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AddTutorialComponent } from './add-tutorial.component';
|
||||
|
||||
describe('AddTutorialComponent', () => {
|
||||
let component: AddTutorialComponent;
|
||||
let fixture: ComponentFixture<AddTutorialComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ AddTutorialComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AddTutorialComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import {Tutorial} from '../../models/tutorial.model';
|
||||
import {TutorialService} from '../../services/tutorial.service';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-add-tutorial',
|
||||
templateUrl: './add-tutorial.component.html',
|
||||
styleUrls: ['./add-tutorial.component.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class AddTutorialComponent implements OnInit {
|
||||
|
||||
tutorial: Tutorial = {
|
||||
title: '',
|
||||
description: '',
|
||||
published: false
|
||||
};
|
||||
submitted = false;
|
||||
|
||||
constructor(private tutorialService: TutorialService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
saveTutorial(): void {
|
||||
const data = {
|
||||
title: this.tutorial.title,
|
||||
description: this.tutorial.description
|
||||
};
|
||||
|
||||
this.tutorialService.create(data)
|
||||
.subscribe(
|
||||
response => {
|
||||
console.log(response);
|
||||
this.submitted = true;
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
newTutorial(): void {
|
||||
this.submitted = false;
|
||||
this.tutorial = {
|
||||
title: '',
|
||||
description: '',
|
||||
published: false
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<div class="col-md-6">
|
||||
<h4>Users List</h4>
|
||||
<ul class="list-group">
|
||||
<li
|
||||
class="list-group-item"
|
||||
*ngFor="let row of rowData; let i = index"
|
||||
|
||||
>
|
||||
{{ row.username }}
|
||||
|
||||
{{ row.email}}
|
||||
{{ row.password}}
|
||||
|
||||
<form>
|
||||
<mat-form-field class="example-chip-list">
|
||||
<mat-chip-grid #chipGrid aria-label="Role selection">
|
||||
@for (role of row.roles; track $index) {
|
||||
<mat-chip-row (removed)="remove(role)">
|
||||
{{role.name}}
|
||||
<button matChipRemove [attr.aria-label]="'remove ' + role.name">
|
||||
<mat-icon>cancel</mat-icon>
|
||||
</button>
|
||||
</mat-chip-row>
|
||||
}
|
||||
</mat-chip-grid>
|
||||
<input
|
||||
name="currentFruit"
|
||||
placeholder="Add role..."
|
||||
#fruitInput
|
||||
[(ngModel)]="currentRole"
|
||||
[matChipInputFor]="chipGrid"
|
||||
[matAutocomplete]="auto"
|
||||
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
|
||||
[formControl]="myControl"
|
||||
(matChipInputTokenEnd)="add($event)"
|
||||
(input)="change($event,filteredRoles(row.roles))"
|
||||
/>
|
||||
<mat-autocomplete [formControl]="ac" autoActiveFirstOption #auto="matAutocomplete" (optionSelected)="selected(row.roles,$event); ">
|
||||
@for (role of filteredRoles(row.roles); track role) {
|
||||
<mat-option [value]="role">{{role.name}}</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.example-chip-list {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { BoardAdminComponent } from './board-admin.component';
|
||||
|
||||
describe('BoardAdminComponent', () => {
|
||||
let component: BoardAdminComponent;
|
||||
let fixture: ComponentFixture<BoardAdminComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ BoardAdminComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(BoardAdminComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,124 @@
|
||||
import {Component, computed, model, OnInit} from '@angular/core';
|
||||
|
||||
import {User} from "../../models/user.model";
|
||||
import {MatChipInputEvent} from "@angular/material/chips";
|
||||
import {MatAutocompleteSelectedEvent} from "@angular/material/autocomplete";
|
||||
import {COMMA, ENTER} from "@angular/cdk/keycodes";
|
||||
import {AsyncPipe} from "@angular/common";
|
||||
import {FormControl} from "@angular/forms";
|
||||
import {Observable, startWith} from "rxjs";
|
||||
import {map} from "rxjs/operators";
|
||||
import {UserService} from '../../services/user.service';
|
||||
import {RolesService} from '../../services/roles.service';
|
||||
import {Role} from '../../models/role';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-board-admin',
|
||||
templateUrl: './board-admin.component.html',
|
||||
styleUrls: ['./board-admin.component.scss'],
|
||||
standalone: false,
|
||||
|
||||
})
|
||||
export class BoardAdminComponent implements OnInit {
|
||||
private gridApi: any;
|
||||
|
||||
readonly separatorKeysCodes: number[] = [ENTER, COMMA];
|
||||
rowData?: User[];
|
||||
allRoles: any;
|
||||
|
||||
readonly currentRole = model('');
|
||||
|
||||
protected myControl = new FormControl('');
|
||||
protected ac = new FormControl('');
|
||||
|
||||
|
||||
constructor(private userService: UserService, private roleService: RolesService ) {
|
||||
this.getAllRoles();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
//this.myControl.valueChanges.pipe(
|
||||
// startWith(''),
|
||||
// map(value => this._filter(value || '')),
|
||||
//);
|
||||
|
||||
this.userService.getAdminBoard().subscribe(
|
||||
(data : any) => {
|
||||
this.rowData = data;
|
||||
},
|
||||
(err : any)=> {
|
||||
this.rowData = JSON.parse(err.error).message;
|
||||
}
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
filteredRoles(roles : Role[]):any {
|
||||
return this.allRoles.filter(
|
||||
(r:Role) => !roles.some((item) => item.id === r.id),
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
// private _filter(value: string): string[] {
|
||||
// const filterValue = value.toLowerCase();
|
||||
// this.ac.
|
||||
// return this.options.filter(option => option.toLowerCase().includes(filterValue));
|
||||
//}
|
||||
|
||||
getAllRoles():any{
|
||||
this.roleService.getAllRoles().subscribe(
|
||||
(data : any) => {
|
||||
this.allRoles = data;
|
||||
},
|
||||
(err : any)=> {
|
||||
this.allRoles = JSON.parse(err.error).message;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
remove(role: string): void {
|
||||
// this.fruits.update(fruits => {
|
||||
// const index = fruits.indexOf(fruit);
|
||||
// if (index < 0) {
|
||||
// return fruits;
|
||||
// }
|
||||
|
||||
// fruits.splice(index, 1);
|
||||
// this.announcer.announce(`Removed ${fruit}`);
|
||||
// return [...fruits];
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
add(event: MatChipInputEvent): void {
|
||||
const value = (event.value || '').trim();
|
||||
|
||||
// Add our fruit
|
||||
if (value) {
|
||||
// this.fruits.update(fruits => [...fruits, value]);
|
||||
}
|
||||
|
||||
// Clear the input value
|
||||
this.currentRole.set('');
|
||||
}
|
||||
|
||||
selected(roles : Role[], event: MatAutocompleteSelectedEvent): void {
|
||||
|
||||
roles.push(event.option.value);
|
||||
this.currentRole.set('');
|
||||
event.option.deselect();
|
||||
}
|
||||
|
||||
|
||||
change($event: Event, roles: Role[]) {
|
||||
|
||||
roles.filter(
|
||||
(r:Role) => !roles.some((item) => item.name?.toLowerCase() === r.id),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<p>board-moderator works!</p>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { BoardModeratorComponent } from './board-moderator.component';
|
||||
|
||||
describe('BoardModeratorComponent', () => {
|
||||
let component: BoardModeratorComponent;
|
||||
let fixture: ComponentFixture<BoardModeratorComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ BoardModeratorComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(BoardModeratorComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-board-moderator',
|
||||
templateUrl: './board-moderator.component.html',
|
||||
styleUrls: ['./board-moderator.component.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class BoardModeratorComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<p>board-user works!</p>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { BoardUserComponent } from './board-user.component';
|
||||
|
||||
describe('BoardUserComponent', () => {
|
||||
let component: BoardUserComponent;
|
||||
let fixture: ComponentFixture<BoardUserComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ BoardUserComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(BoardUserComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-board-user',
|
||||
templateUrl: './board-user.component.html',
|
||||
styleUrls: ['./board-user.component.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class BoardUserComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<h1 mat-dialog-title>Hi </h1>
|
||||
<div mat-dialog-content>
|
||||
<p>User name</p>
|
||||
<mat-form-field class="fillField">
|
||||
<input matInput [(ngModel)]="data.username">
|
||||
</mat-form-field>
|
||||
<p>Password</p>
|
||||
<mat-form-field class="fillField">
|
||||
<mat-label>Enter your password</mat-label>
|
||||
<input matInput
|
||||
[type]="hide() ? 'password' : 'text'"
|
||||
[(ngModel)]="data.password"
|
||||
/>
|
||||
<button
|
||||
mat-icon-button
|
||||
matSuffix
|
||||
(click)="clickEvent($event)"
|
||||
[attr.aria-label]="'Hide password'"
|
||||
[attr.aria-pressed]="hide()"
|
||||
>
|
||||
<mat-icon>{{hide() ? 'visibility_off' : 'visibility'}}</mat-icon>
|
||||
</button>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div mat-dialog-actions>
|
||||
<button mat-button mat-dialog-close (click)="onNoClick()">No Thanks</button>
|
||||
<button mat-button [mat-dialog-close]="data" cdkFocusInitial>Ok</button>
|
||||
</div>
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
.fillField{
|
||||
width: 100%;
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DialogComponent } from './dialog.component';
|
||||
|
||||
describe('DialogComponent', () => {
|
||||
let component: DialogComponent;
|
||||
let fixture: ComponentFixture<DialogComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [DialogComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(DialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import {ChangeDetectionStrategy, Component, Inject, inject, signal} from '@angular/core';
|
||||
import {MatButtonModule} from "@angular/material/button";
|
||||
import {
|
||||
MAT_DIALOG_DATA,
|
||||
MatDialogActions,
|
||||
MatDialogClose,
|
||||
MatDialogContent,
|
||||
MatDialogRef,
|
||||
MatDialogTitle
|
||||
} from "@angular/material/dialog";
|
||||
|
||||
|
||||
import {MatFormField, MatLabel, MatSuffix} from "@angular/material/form-field";
|
||||
import {MatInput} from "@angular/material/input";
|
||||
import {FormsModule} from "@angular/forms";
|
||||
import {MatIcon} from "@angular/material/icon";
|
||||
import {DialogData} from "../../app.component";
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-dialog',
|
||||
imports: [MatButtonModule, MatLabel, MatDialogActions, MatDialogClose, MatDialogTitle, MatDialogContent, MatFormField, MatInput, FormsModule, MatIcon, MatSuffix],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
templateUrl: './dialog.component.html',
|
||||
styleUrl: './dialog.component.scss'
|
||||
})
|
||||
export class DialogComponent {
|
||||
readonly dialogRef = inject(MatDialogRef<DialogComponent>);
|
||||
hide = signal(true);
|
||||
|
||||
constructor(
|
||||
@Inject(MAT_DIALOG_DATA) public data:DialogData) {}
|
||||
|
||||
onNoClick() {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
|
||||
clickEvent(event: MouseEvent) {
|
||||
this.hide.set(!this.hide());
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<div class="container">
|
||||
<header class="jumbotron">
|
||||
<p>{{ content }}</p>
|
||||
</header>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { HomeComponent } from './home.component';
|
||||
|
||||
describe('HomeComponent', () => {
|
||||
let component: HomeComponent;
|
||||
let fixture: ComponentFixture<HomeComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ HomeComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(HomeComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import {UserService} from '../../services/user.service';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
templateUrl: './home.component.html',
|
||||
styleUrls: ['./home.component.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class HomeComponent implements OnInit {
|
||||
content?: string;
|
||||
|
||||
constructor(private userService: UserService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
// this.userService.getPublicContent().subscribe(
|
||||
// (data : any) => {
|
||||
// this.content = data;
|
||||
// },
|
||||
// (err : any) => {
|
||||
// this.content = JSON.parse(err.error).message;
|
||||
// }
|
||||
// );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<div class="col-md-12">
|
||||
<div class="card card-container">
|
||||
<img
|
||||
id="profile-img"
|
||||
src="//ssl.gstatic.com/accounts/ui/avatar_2x.png"
|
||||
class="profile-img-card"
|
||||
/>
|
||||
<form
|
||||
*ngIf="!isLoggedIn"
|
||||
name="form"
|
||||
(ngSubmit)="f.form.valid && onSubmit()"
|
||||
#f="ngForm"
|
||||
novalidate
|
||||
>
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="username"
|
||||
[(ngModel)]="form.username"
|
||||
required
|
||||
#username="ngModel"
|
||||
/>
|
||||
<div
|
||||
class="alert alert-danger"
|
||||
role="alert"
|
||||
*ngIf="username.errors && f.submitted"
|
||||
>
|
||||
Username is required!
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
class="form-control"
|
||||
id="password"
|
||||
[(ngModel)]="form.password"
|
||||
required
|
||||
minlength="6"
|
||||
#password="ngModel"
|
||||
/>
|
||||
<div
|
||||
class="alert alert-danger"
|
||||
role="alert"
|
||||
*ngIf="password.errors && f.submitted"
|
||||
>
|
||||
<div *ngIf="password.errors['required']">Password is required</div>
|
||||
<div *ngIf="password.errors['minlength']">
|
||||
Password must be at least 6 characters
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn btn-primary btn-block">
|
||||
Login
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div
|
||||
class="alert alert-danger"
|
||||
role="alert"
|
||||
*ngIf="f.submitted && isLoginFailed"
|
||||
>
|
||||
Login failed: {{ errorMessage }}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="alert alert-success" *ngIf="isLoggedIn">
|
||||
Logged in as {{ roles }}.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LoginComponent } from './login.component';
|
||||
|
||||
describe('LoginComponent', () => {
|
||||
let component: LoginComponent;
|
||||
let fixture: ComponentFixture<LoginComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ LoginComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LoginComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
import {FormsModule} from "@angular/forms";
|
||||
import {NgIf} from "@angular/common";
|
||||
import {AuthService} from '../../services/auth.service';
|
||||
import {TokenStorageService} from '../../services/token-storage.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
templateUrl: './login.component.html',
|
||||
imports: [
|
||||
FormsModule,
|
||||
NgIf
|
||||
],
|
||||
styleUrls: ['./login.component.scss']
|
||||
})
|
||||
export class LoginComponent implements OnInit {
|
||||
|
||||
form: any = {
|
||||
username: null,
|
||||
password: null
|
||||
};
|
||||
isLoggedIn = false;
|
||||
isLoginFailed = false;
|
||||
errorMessage = '';
|
||||
roles: string[] = [];
|
||||
|
||||
constructor(private authService: AuthService, private tokenStorage: TokenStorageService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.tokenStorage.getToken()) {
|
||||
this.isLoggedIn = true;
|
||||
this.roles = this.tokenStorage.getUser().roles;
|
||||
}
|
||||
}
|
||||
|
||||
onSubmit(): void {
|
||||
const { username, password } = this.form;
|
||||
|
||||
this.authService.login(username, password).subscribe(
|
||||
data => {
|
||||
this.tokenStorage.saveToken(data.accessToken);
|
||||
this.tokenStorage.saveUser(data);
|
||||
|
||||
this.isLoginFailed = false;
|
||||
this.isLoggedIn = true;
|
||||
this.roles = this.tokenStorage.getUser().roles;
|
||||
this.reloadPage();
|
||||
},
|
||||
err => {
|
||||
this.errorMessage = err.error.message;
|
||||
this.isLoginFailed = true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
reloadPage(): void {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<div class="container" *ngIf="currentUser; else loggedOut">
|
||||
<header class="jumbotron">
|
||||
<h3>
|
||||
<strong>{{ currentUser.username }}</strong> Profile
|
||||
</h3>
|
||||
</header>
|
||||
<p>
|
||||
<strong>Token:</strong>
|
||||
{{ currentUser.accessToken.substring(0, 20) }} ...
|
||||
{{ currentUser.accessToken.substr(currentUser.accessToken.length - 20) }}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Email:</strong>
|
||||
{{ currentUser.email }}
|
||||
</p>
|
||||
<strong>Roles:</strong>
|
||||
<ul>
|
||||
<li *ngFor="let role of currentUser.roles">
|
||||
{{ role }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<ng-template #loggedOut>
|
||||
Please login.
|
||||
</ng-template>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ProfileComponent } from './profile.component';
|
||||
|
||||
describe('ProfileComponent', () => {
|
||||
let component: ProfileComponent;
|
||||
let fixture: ComponentFixture<ProfileComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ ProfileComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ProfileComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import {TokenStorageService} from '../../services/token-storage.service';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-profile',
|
||||
templateUrl: './profile.component.html',
|
||||
styleUrls: ['./profile.component.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class ProfileComponent implements OnInit {
|
||||
currentUser: any;
|
||||
|
||||
constructor(private token: TokenStorageService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.currentUser = this.token.getUser();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<div class="col-md-12">
|
||||
<div id="register">
|
||||
<img id="foto"
|
||||
id="profile-img"
|
||||
src="//ssl.gstatic.com/accounts/ui/avatar_2x.png"
|
||||
class="profile-img-card"
|
||||
/>
|
||||
<form
|
||||
*ngIf="!isSuccessful"
|
||||
name="form"
|
||||
(ngSubmit)="f.form.valid && onSubmit()"
|
||||
#f="ngForm"
|
||||
novalidate
|
||||
>
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="username"
|
||||
[(ngModel)]="form.username"
|
||||
[ngModelOptions]="{standalone: true}"
|
||||
required
|
||||
minlength="3"
|
||||
maxlength="20"
|
||||
#username="ngModel"
|
||||
/>
|
||||
<div class="alert-danger" *ngIf="username.errors && f.submitted">
|
||||
<div *ngIf="username.errors['required']">Username is required</div>
|
||||
<div *ngIf="username.errors['minlength']">
|
||||
Username must be at least 3 characters
|
||||
</div>
|
||||
<div *ngIf="username.errors['maxlength']">
|
||||
Username must be at most 20 characters
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
class="form-control"
|
||||
id="email"
|
||||
[(ngModel)]="form.email"
|
||||
[ngModelOptions]="{standalone: true}"
|
||||
required
|
||||
email
|
||||
#email="ngModel"
|
||||
/>
|
||||
<div class="alert-danger" *ngIf="email.errors && f.submitted">
|
||||
<div *ngIf="email.errors['required']">Email is required</div>
|
||||
<div *ngIf="email.errors['email']">
|
||||
Email must be a valid email address
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
class="form-control"
|
||||
id="password"
|
||||
[(ngModel)]="form.password"
|
||||
[ngModelOptions]="{standalone: true}"
|
||||
required
|
||||
minlength="6"
|
||||
#password="ngModel"
|
||||
/>
|
||||
<div class="alert-danger" *ngIf="password.errors && f.submitted">
|
||||
<div *ngIf="password.errors['required']">Password is required</div>
|
||||
<div *ngIf="password.errors['minlength']">
|
||||
Password must be at least 6 characters
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn btn-primary btn-block">Sign Up</button>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning" *ngIf="f.submitted && isSignUpFailed">
|
||||
Signup failed!<br />{{ errorMessage }}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="alert alert-success" *ngIf="isSuccessful">
|
||||
Your registration is successful!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { RegisterComponent } from './register.component';
|
||||
|
||||
describe('RegisterComponent', () => {
|
||||
let component: RegisterComponent;
|
||||
let fixture: ComponentFixture<RegisterComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ RegisterComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(RegisterComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import {AuthService} from '../../services/auth.service';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-register',
|
||||
templateUrl: './register.component.html',
|
||||
styleUrls: ['./register.component.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class RegisterComponent implements OnInit {
|
||||
form: any = {
|
||||
username: null,
|
||||
email: null,
|
||||
password: null
|
||||
};
|
||||
isSuccessful = false;
|
||||
isSignUpFailed = false;
|
||||
errorMessage = '';
|
||||
|
||||
constructor(private authService: AuthService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
onSubmit(): void {
|
||||
const { username, email, password } = this.form;
|
||||
|
||||
this.authService.register(username, email, password).subscribe(
|
||||
data => {
|
||||
console.log(data);
|
||||
this.isSuccessful = true;
|
||||
this.isSignUpFailed = false;
|
||||
},
|
||||
err => {
|
||||
this.errorMessage = err.error.message;
|
||||
this.isSignUpFailed = true;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<div>
|
||||
<div *ngIf="currentTutorial.id" class="edit-form">
|
||||
<h4>Tutorial</h4>
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label for="title">Title</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="title"
|
||||
[(ngModel)]="currentTutorial.title"
|
||||
name="title"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="description">Description</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="description"
|
||||
[(ngModel)]="currentTutorial.description"
|
||||
name="description"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label><strong>Status:</strong></label>
|
||||
{{ currentTutorial.published ? "Published" : "Pending" }}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<button
|
||||
class="badge badge-primary mr-2"
|
||||
*ngIf="currentTutorial.published"
|
||||
(click)="updatePublished(false)"
|
||||
>
|
||||
UnPublish
|
||||
</button>
|
||||
<button
|
||||
*ngIf="!currentTutorial.published"
|
||||
class="badge badge-primary mr-2"
|
||||
(click)="updatePublished(true)"
|
||||
>
|
||||
Publish
|
||||
</button>
|
||||
|
||||
<button class="badge badge-danger mr-2" (click)="deleteTutorial()">
|
||||
Delete
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="badge badge-success mb-2"
|
||||
(click)="updateTutorial()"
|
||||
>
|
||||
Update
|
||||
</button>
|
||||
<p>{{ message }}</p>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!currentTutorial.id">
|
||||
<br />
|
||||
<p>Cannot access this Tutorial...</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,4 @@
|
||||
.edit-form {
|
||||
max-width: 400px;
|
||||
margin: auto;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TutorialDetailsComponent } from './tutorial-details.component';
|
||||
|
||||
describe('TutorialDetailsComponent', () => {
|
||||
let component: TutorialDetailsComponent;
|
||||
let fixture: ComponentFixture<TutorialDetailsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ TutorialDetailsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TutorialDetailsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import {Tutorial} from '../../models/tutorial.model';
|
||||
import {TutorialService} from '../../services/tutorial.service';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-tutorial-details',
|
||||
templateUrl: './tutorial-details.component.html',
|
||||
styleUrls: ['./tutorial-details.component.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class TutorialDetailsComponent implements OnInit {
|
||||
|
||||
currentTutorial: Tutorial = {
|
||||
title: '',
|
||||
description: '',
|
||||
published: false
|
||||
};
|
||||
message = '';
|
||||
|
||||
constructor(
|
||||
private tutorialService: TutorialService,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.message = '';
|
||||
this.getTutorial(this.route.snapshot.params['id']);
|
||||
}
|
||||
|
||||
getTutorial(id: string): void {
|
||||
this.tutorialService.get(id)
|
||||
.subscribe(
|
||||
data => {
|
||||
this.currentTutorial = data;
|
||||
console.log(data);
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
updatePublished(status: boolean): void {
|
||||
const data = {
|
||||
title: this.currentTutorial.title,
|
||||
description: this.currentTutorial.description,
|
||||
published: status
|
||||
};
|
||||
|
||||
this.message = '';
|
||||
|
||||
this.tutorialService.update(this.currentTutorial.id, data)
|
||||
.subscribe(
|
||||
response => {
|
||||
this.currentTutorial.published = status;
|
||||
console.log(response);
|
||||
this.message = response.message ? response.message : 'The status was updated successfully!';
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
updateTutorial(): void {
|
||||
this.message = '';
|
||||
|
||||
this.tutorialService.update(this.currentTutorial.id, this.currentTutorial)
|
||||
.subscribe(
|
||||
response => {
|
||||
console.log(response);
|
||||
this.message = response.message ? response.message : 'This tutorial was updated successfully!';
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
deleteTutorial(): void {
|
||||
this.tutorialService.delete(this.currentTutorial.id)
|
||||
.subscribe(
|
||||
response => {
|
||||
console.log(response);
|
||||
this.router.navigate(['/tutorials']);
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<div class="list row">
|
||||
<div class="col-md-8">
|
||||
<div class="input-group mb-3">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Search by title"
|
||||
[(ngModel)]="title"
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<button
|
||||
class="btn btn-outline-secondary"
|
||||
type="button"
|
||||
(click)="searchTitle()"
|
||||
>
|
||||
Search
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h4>Tutorials List</h4>
|
||||
<ul class="list-group">
|
||||
<li
|
||||
class="list-group-item"
|
||||
*ngFor="let tutorial of tutorials; let i = index"
|
||||
[class.active]="i == currentIndex"
|
||||
(click)="setActiveTutorial(tutorial, i)"
|
||||
>
|
||||
{{ tutorial.title }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<button class="m-3 btn btn-sm btn-danger" (click)="removeAllTutorials()">
|
||||
Remove All
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div *ngIf="currentTutorial.id">
|
||||
<h4>Tutorial</h4>
|
||||
<div>
|
||||
<label><strong>Title:</strong></label> {{ currentTutorial.title }}
|
||||
</div>
|
||||
<div>
|
||||
<label><strong>Description:</strong></label>
|
||||
{{ currentTutorial.description }}
|
||||
</div>
|
||||
<div>
|
||||
<label><strong>Status:</strong></label>
|
||||
{{ currentTutorial.published ? "Published" : "Pending" }}
|
||||
</div>
|
||||
|
||||
<a class="badge badge-warning" routerLink="/tutorials/{{ currentTutorial.id }}">
|
||||
Edit
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!currentTutorial">
|
||||
<br />
|
||||
<p>Please click on a Tutorial...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,5 @@
|
||||
.list {
|
||||
text-align: left;
|
||||
max-width: 750px;
|
||||
margin: auto;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TutorialsListComponent } from './tutorials-list.component';
|
||||
|
||||
describe('TutorialsListComponent', () => {
|
||||
let component: TutorialsListComponent;
|
||||
let fixture: ComponentFixture<TutorialsListComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ TutorialsListComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TutorialsListComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
|
||||
import {Tutorial} from '../../models/tutorial.model';
|
||||
import {TutorialService} from '../../services/tutorial.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tutorials-list',
|
||||
templateUrl: './tutorials-list.component.html',
|
||||
styleUrls: ['./tutorials-list.component.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class TutorialsListComponent implements OnInit {
|
||||
|
||||
tutorials?: Tutorial[];
|
||||
currentTutorial: Tutorial = {};
|
||||
currentIndex = -1;
|
||||
title = '';
|
||||
|
||||
constructor(private tutorialService: TutorialService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.retrieveTutorials();
|
||||
}
|
||||
|
||||
retrieveTutorials(): void {
|
||||
this.tutorialService.getAll()
|
||||
.subscribe(
|
||||
data => {
|
||||
this.tutorials = data;
|
||||
console.log(data);
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
refreshList(): void {
|
||||
this.retrieveTutorials();
|
||||
this.currentTutorial = {};
|
||||
this.currentIndex = -1;
|
||||
}
|
||||
|
||||
setActiveTutorial(tutorial: Tutorial, index: number): void {
|
||||
this.currentTutorial = tutorial;
|
||||
this.currentIndex = index;
|
||||
}
|
||||
|
||||
removeAllTutorials(): void {
|
||||
this.tutorialService.deleteAll()
|
||||
.subscribe(
|
||||
response => {
|
||||
console.log(response);
|
||||
this.refreshList();
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
searchTitle(): void {
|
||||
this.currentTutorial = {};
|
||||
this.currentIndex = -1;
|
||||
|
||||
this.tutorialService.findByTitle(this.title)
|
||||
.subscribe(
|
||||
data => {
|
||||
this.tutorials = data;
|
||||
console.log(data);
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { HTTP_INTERCEPTORS, HttpEvent } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
|
||||
|
||||
import { TokenStorageService } from '../services/token-storage.service';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
const TOKEN_HEADER_KEY = 'Authorization'; // for Spring Boot back-end
|
||||
|
||||
@Injectable()
|
||||
export class AuthInterceptor implements HttpInterceptor {
|
||||
constructor(private token: TokenStorageService) { }
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
let authReq = req;
|
||||
const token = this.token.getToken();
|
||||
if (token != null) {
|
||||
authReq = req.clone({ headers: req.headers.set(TOKEN_HEADER_KEY, 'Bearer ' + token) });
|
||||
}
|
||||
return next.handle(authReq);
|
||||
}
|
||||
}
|
||||
|
||||
export const authInterceptorProviders = [
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
|
||||
];
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Role } from './role';
|
||||
|
||||
describe('Role', () => {
|
||||
it('should create an instance', () => {
|
||||
expect(new Role()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
export class Role {
|
||||
id?:any;
|
||||
name?:string;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export class Setting {
|
||||
id?:any;
|
||||
name?:string;
|
||||
value?:boolean;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Tutorial } from './tutorial.model';
|
||||
|
||||
describe('Tutorial', () => {
|
||||
it('should create an instance', () => {
|
||||
expect(new Tutorial()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
export class Tutorial {
|
||||
id?: any;
|
||||
title?: string;
|
||||
description?: string;
|
||||
published?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { User } from './user.model';
|
||||
|
||||
describe('User', () => {
|
||||
it('should create an instance', () => {
|
||||
expect(new User()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
export class User {
|
||||
id?:any;
|
||||
username?:string;
|
||||
email?:string;
|
||||
password?:string;
|
||||
roles?:any;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
describe('AuthService', () => {
|
||||
let service: AuthService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(AuthService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
const AUTH_API = 'http://localhost:8080/api/auth/';
|
||||
|
||||
const httpOptions = {
|
||||
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
|
||||
};
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthService {
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
login(username: string, password: string): Observable<any> {
|
||||
return this.http.post(AUTH_API + 'signin', {
|
||||
username,
|
||||
password
|
||||
}, httpOptions);
|
||||
}
|
||||
|
||||
register(username: string, email: string, password: string): Observable<any> {
|
||||
return this.http.post(AUTH_API + 'signup', {
|
||||
username,
|
||||
email,
|
||||
password
|
||||
}, httpOptions);
|
||||
}
|
||||
|
||||
logout(): Observable<any> {
|
||||
return this.http.post(AUTH_API + 'signout', { }, httpOptions);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { RolesService } from './roles.service';
|
||||
|
||||
describe('RolesService', () => {
|
||||
let service: RolesService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(RolesService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Role } from '../models/role';
|
||||
|
||||
const API_URL = 'http://localhost:8080/api/roles';
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class RolesService {
|
||||
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
getAllRoles(): Observable<Role[]> {
|
||||
return this.http.get<Role[]>(API_URL);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TokenStorageService } from './token-storage.service';
|
||||
|
||||
describe('TokenStorageService', () => {
|
||||
let service: TokenStorageService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(TokenStorageService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
const TOKEN_KEY = 'auth-token';
|
||||
const USER_KEY = 'auth-user';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class TokenStorageService {
|
||||
constructor() { }
|
||||
|
||||
signOut(): void {
|
||||
window.sessionStorage.clear();
|
||||
}
|
||||
|
||||
public saveToken(token: string): void {
|
||||
window.sessionStorage.removeItem(TOKEN_KEY);
|
||||
window.sessionStorage.setItem(TOKEN_KEY, token);
|
||||
}
|
||||
|
||||
public getToken(): string | null {
|
||||
return window.sessionStorage.getItem(TOKEN_KEY);
|
||||
}
|
||||
|
||||
public saveUser(user: any): void {
|
||||
window.sessionStorage.removeItem(USER_KEY);
|
||||
window.sessionStorage.setItem(USER_KEY, JSON.stringify(user));
|
||||
}
|
||||
|
||||
public getUser(): any {
|
||||
const user = window.sessionStorage.getItem(USER_KEY);
|
||||
if (user) {
|
||||
return JSON.parse(user);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
clean(): void {
|
||||
window.sessionStorage.clear();
|
||||
}
|
||||
|
||||
public isLoggedIn(): boolean {
|
||||
const user = window.sessionStorage.getItem(USER_KEY);
|
||||
if (user) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TutorialService } from './tutorial.service';
|
||||
|
||||
describe('TutorialService', () => {
|
||||
let service: TutorialService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(TutorialService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Tutorial } from '../models/tutorial.model';
|
||||
|
||||
const baseUrl = 'http://localhost:8080/api/tutorials';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class TutorialService {
|
||||
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
getAll(): Observable<Tutorial[]> {
|
||||
return this.http.get<Tutorial[]>(baseUrl);
|
||||
}
|
||||
|
||||
get(id: any): Observable<Tutorial> {
|
||||
return this.http.get(`${baseUrl}/${id}`);
|
||||
}
|
||||
|
||||
create(data: any): Observable<any> {
|
||||
return this.http.post(baseUrl, data);
|
||||
}
|
||||
|
||||
update(id: any, data: any): Observable<any> {
|
||||
return this.http.put(`${baseUrl}/${id}`, data);
|
||||
}
|
||||
|
||||
delete(id: any): Observable<any> {
|
||||
return this.http.delete(`${baseUrl}/${id}`);
|
||||
}
|
||||
|
||||
deleteAll(): Observable<any> {
|
||||
return this.http.delete(baseUrl);
|
||||
}
|
||||
|
||||
findByTitle(title: any): Observable<Tutorial[]> {
|
||||
return this.http.get<Tutorial[]>(`${baseUrl}?title=${title}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { UserService } from './user.service';
|
||||
|
||||
describe('UserService', () => {
|
||||
let service: UserService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(UserService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import {User} from "../models/user.model";
|
||||
import {Tutorial} from "../models/tutorial.model";
|
||||
|
||||
const API_URL = 'http://localhost:8080/api/users';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UserService {
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
getPublicContent(): Observable<any> {
|
||||
return this.http.get(API_URL + '/all', { responseType: 'text' });
|
||||
}
|
||||
|
||||
getUserBoard(): Observable<any> {
|
||||
return this.http.get(API_URL + '/user', { responseType: 'text' });
|
||||
}
|
||||
|
||||
getModeratorBoard(): Observable<any> {
|
||||
return this.http.get(API_URL + '/mod', { responseType: 'text' });
|
||||
}
|
||||
|
||||
getAdminBoard(): Observable<User[]> {
|
||||
return this.http.get<User[]>(API_URL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JambotronUi</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>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,14 @@
|
||||
import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import {AppModule} from './app/app.module';
|
||||
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule)
|
||||
|
||||
.catch(
|
||||
// err => console.error(err)
|
||||
(e) => {
|
||||
console.error(e.message);
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,4 @@
|
||||
/* 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; }
|
||||
@@ -0,0 +1,17 @@
|
||||
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/app",
|
||||
"types": [
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"skipLibCheck": true,
|
||||
"isolatedModules": true,
|
||||
"experimentalDecorators": true,
|
||||
"importHelpers": true,
|
||||
"target": "ES2022",
|
||||
"module": "preserve"
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"typeCheckHostBindings": true,
|
||||
"strictTemplates": true
|
||||
},
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user