Commit 32824402 authored by mechtelm's avatar mechtelm
Browse files

Make frontend work with admin (users and config)

parent 3946a5dc
{
"version": 2,
"version": 1,
"projects": {
"api": "apps/api",
"dto": "libs/dto",
"frontend": "apps/frontend",
"frontend-e2e": "apps/frontend-e2e"
"api": {
"root": "apps/api",
"sourceRoot": "apps/api/src",
"projectType": "application",
"architect": {
"build": {
"builder": "@nrwl/node:build",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/apps/api",
"main": "apps/api/src/main.ts",
"tsConfig": "apps/api/tsconfig.app.json",
"assets": ["apps/api/src/assets"]
},
"configurations": {
"production": {
"optimization": true,
"extractLicenses": true,
"inspect": false,
"fileReplacements": [
{
"replace": "apps/api/src/environments/environment.ts",
"with": "apps/api/src/environments/environment.prod.ts"
}
]
}
}
},
"serve": {
"builder": "@nrwl/node:execute",
"options": {
"buildTarget": "api:build"
}
},
"lint": {
"builder": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/api/**/*.ts"]
}
},
"test": {
"builder": "@nrwl/jest:jest",
"outputs": ["coverage/apps/api"],
"options": {
"jestConfig": "apps/api/jest.config.js",
"passWithNoTests": true
}
}
}
},
"frontend": {
"projectType": "application",
"root": "apps/frontend",
"sourceRoot": "apps/frontend/src",
"prefix": "studio-lite",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/apps/frontend",
"index": "apps/frontend/src/index.html",
"main": "apps/frontend/src/main.ts",
"polyfills": "apps/frontend/src/polyfills.ts",
"tsConfig": "apps/frontend/tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": ["apps/frontend/src/assets"],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/purple-green.css",
"apps/frontend/src/styles.scss",
"apps/frontend/src/iqb-theme3.scss"
],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"fileReplacements": [
{
"replace": "apps/frontend/src/environments/environment.ts",
"with": "apps/frontend/src/environments/environment.prod.ts"
}
],
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "frontend:build:production"
},
"development": {
"browserTarget": "frontend:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "frontend:build"
}
},
"lint": {
"builder": "@nrwl/linter:eslint",
"options": {
"lintFilePatterns": [
"apps/frontend/src/**/*.ts",
"apps/frontend/src/**/*.html"
]
}
},
"test": {
"builder": "@nrwl/jest:jest",
"outputs": ["coverage/apps/frontend"],
"options": {
"jestConfig": "apps/frontend/jest.config.js",
"passWithNoTests": true
}
}
},
"tags": []
},
"frontend-e2e": {
"root": "apps/frontend-e2e",
"sourceRoot": "apps/frontend-e2e/src",
"projectType": "application",
"architect": {
"e2e": {
"builder": "@nrwl/cypress:cypress",
"options": {
"cypressConfig": "apps/frontend-e2e/cypress.json",
"devServerTarget": "frontend:serve:development"
},
"configurations": {
"production": {
"devServerTarget": "frontend:serve:production"
}
}
},
"lint": {
"builder": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/frontend-e2e/**/*.{js,ts}"]
}
}
},
"tags": [],
"implicitDependencies": ["frontend"]
},
"iqb-components": {
"projectType": "library",
"root": "libs/iqb-components",
"sourceRoot": "libs/iqb-components/src",
"prefix": "studio-lite",
"architect": {
"test": {
"builder": "@nrwl/jest:jest",
"outputs": ["coverage/libs/iqb-components"],
"options": {
"jestConfig": "libs/iqb-components/jest.config.js",
"passWithNoTests": true
}
},
"lint": {
"builder": "@nrwl/linter:eslint",
"options": {
"lintFilePatterns": [
"libs/iqb-components/src/**/*.ts",
"libs/iqb-components/src/**/*.html"
]
}
}
},
"tags": []
},
"dto": {
"projectType": "library",
"root": "libs/dto",
"sourceRoot": "libs/dto/src",
"prefix": "studio-lite",
"architect": {
"test": {
"builder": "@nrwl/jest:jest",
"outputs": ["coverage/libs/iqb-components"],
"options": {
"jestConfig": "libs/dto/jest.config.js",
"passWithNoTests": true
}
},
"lint": {
"builder": "@nrwl/linter:eslint",
"options": {
"lintFilePatterns": [
"libs/dto/src/**/*.ts",
"libs/dto/src/**/*.html"
]
}
}
},
"tags": []
}
}
}
import { Module } from '@nestjs/common';
import {DatabaseModule} from "../database/database.module";
import {AuthModule} from "../auth/auth.module";
import {UsersController} from "./users/users.controller";
import {ConfigController} from "./config/config.controller";
@Module({})
@Module({
imports: [
DatabaseModule,
AuthModule
],
controllers: [
UsersController,
ConfigController
]
})
export class AppAdminModule {}
......@@ -2,7 +2,7 @@ import {Column, Entity, PrimaryColumn} from 'typeorm';
import {ConfigFullDto} from "../../../../../../libs/dto/src";
@Entity()
class Setting {
class AppConfig {
@PrimaryColumn()
public key: string;
......@@ -12,4 +12,4 @@ class Setting {
public content: ConfigFullDto;
}
export default Setting;
export default AppConfig;
import { Injectable } from '@nestjs/common';
import {InjectRepository} from "@nestjs/typeorm";
import {Repository} from "typeorm";
import Setting from "../entities/config.entity";
import AppConfig from "../entities/config.entity";
import {ConfigFullDto} from "../../../../../../libs/dto/src";
@Injectable()
export class ConfigService {
constructor(
@InjectRepository(Setting)
private settingsRepository: Repository<Setting>,
@InjectRepository(AppConfig)
private settingsRepository: Repository<AppConfig>,
) {}
async findConfig(): Promise<ConfigFullDto> {
......@@ -17,7 +17,7 @@ export class ConfigService {
return setting.content as ConfigFullDto
} else {
return <ConfigFullDto> {
appTitle: 'IQB-Studio-Lite',
appTitle: 'IQB Personal-DB',
introHtml: '<p>Bitte ändern Sie diesen Text über die Admin-Funktion.</p>',
imprintHtml: '<p>Bitte ändern Sie diesen Text über die Admin-Funktion.</p>'
}
......
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { UsersComponent } from './users/users.component';
import { AdminComponent } from './admin.component';
import { SettingsComponent } from './settings/settings.component';
const routes: Routes = [
{
path: '',
component: AdminComponent,
children: [
{ path: '', redirectTo: 'users', pathMatch: 'full' },
{ path: 'users', component: UsersComponent },
{ path: 'settings', component: SettingsComponent },
{ path: '**', component: UsersComponent }
]
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class AdminRoutingModule { }
import { Component } from '@angular/core';
@Component({
template: `
<div class="page-body">
<div class="admin-background">
<nav mat-tab-nav-bar>
<a mat-tab-link
*ngFor="let link of navLinks"
[routerLink]="link.path"
routerLinkActive #rla="routerLinkActive"
[active]="rla.isActive">
{{link.label}}
</a>
</nav>
<router-outlet></router-outlet>
</div>
</div>
`,
styles: [`
.admin-background {
box-shadow: 5px 10px 20px black;
background-color: white;
padding: 25px;
margin: 0 15px 15px 15px;
height: calc(100% - 65px);
}
.communication-error-message {
color: red;
padding: 10px 50px;
}
`]
})
export class AdminComponent {
navLinks = [
{ path: 'users', label: 'Admins' },
{ path: 'settings', label: 'Einstellungen' }
];
}
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
import { MatTabsModule } from '@angular/material/tabs';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MAT_DATE_LOCALE, MatNativeDateModule } from '@angular/material/core';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatBadgeModule } from '@angular/material/badge';
import { FlexLayoutModule } from '@angular/flex-layout';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import { MatChipsModule } from '@angular/material/chips';
import { AdminRoutingModule } from './admin-routing.module';
import { UsersComponent } from './users/users.component';
import { AdminComponent } from './admin.component';
import { BackendService } from './backend.service';
import { SettingsComponent } from './settings/settings.component';
import { AppConfigComponent } from './settings/app-config.component';
import {HTTP_INTERCEPTORS} from "@angular/common/http";
import {AuthInterceptor} from "./auth.interceptor";
import {EditUserComponent} from "./users/edituser.component";
import {IqbComponentsModule} from "../../../../../libs/iqb-components/src";
@NgModule({
imports: [
CommonModule,
AdminRoutingModule,
IqbComponentsModule,
MatTableModule,
MatTabsModule,
MatIconModule,
MatSelectModule,
MatCheckboxModule,
MatSortModule,
ReactiveFormsModule,
MatProgressSpinnerModule,
MatDialogModule,
MatButtonModule,
MatCardModule,
MatTooltipModule,
MatFormFieldModule,
MatInputModule,
MatToolbarModule,
MatSnackBarModule,
MatNativeDateModule,
MatDatepickerModule,
MatBadgeModule,
FlexLayoutModule,
MatChipsModule,
FormsModule
],
exports: [
AdminComponent
],
declarations: [
UsersComponent,
AdminComponent,
EditUserComponent,
SettingsComponent,
AppConfigComponent
],
providers: [
BackendService,
[
{ provide: MAT_DATE_LOCALE, useValue: 'de-DE' },
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
}
]
],
entryComponents: [
EditUserComponent
]
})
export class AdminModule { }
import { Injectable } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor
} from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class AuthInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
const idToken = localStorage.getItem("id_token");
if (idToken) {
const cloned = req.clone({
headers: req.headers.set("Authorization",
"Bearer " + idToken)
});
return next.handle(cloned);
}
else {
return next.handle(req);
}
}
}
import { Injectable, Inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import {Observable, of, throwError} from 'rxjs';
import {catchError, map} from 'rxjs/operators';
import {ConfigFullDto, CreateUserDto, UserFullDto, UserInListDto} from "../../../../../libs/dto/src";
@Injectable({
providedIn: 'root'
})
export class BackendService {
constructor(
@Inject('SERVER_URL') private readonly serverUrl: string,
private http: HttpClient
) {}
getUsers(): Observable<UserInListDto[]> {
return this.http
.get<UserInListDto[]>(`${this.serverUrl}admin/users`)
.pipe(
catchError(err => [])
);
}
addUser(newUser: CreateUserDto): Observable<boolean> {
return this.http
.post(`${this.serverUrl}admin/users`, newUser)
.pipe(
map(() => true)
);
}
changeUserData(newData: UserFullDto): Observable<boolean> {
return this.http
.patch(`${this.serverUrl}admin/users`,newData)
.pipe(
map(() => true)
);
}
deleteUsers(users: number[]): Observable<boolean> {
return this.http
.delete(`${this.serverUrl}admin/users/${users.join(';')}`)
.pipe(
map(() => true)
);
}
getConfig(): Observable<ConfigFullDto | null> {
return this.http
.get<ConfigFullDto | null>(`${this.serverUrl}admin/settings/config`, {})
.pipe(
catchError(() => of(null))
);
}
setAppConfig(appConfig: ConfigFullDto): Observable<boolean> {
return this.http
.patch(`${this.serverUrl}admin/settings/config`, appConfig)
.pipe(
map(() => true)
)
}
}
export { AdminComponent } from './admin.component';
export { AdminModule } from './admin.module';
<form [formGroup]="configForm" *ngIf="appConfig" fxFlex fxLayout="column" fxLayoutAlign="start stretch">
<p>Warnung auf der Startseite</p>
<div class="block-ident" fxLayout="column">
<mat-form-field>
<mat-label>Text</mat-label>
<input matInput formControlName="globalWarningText" placeholder="Warnung">
</mat-form-field>
<div fxLayout="row wrap" fxLayoutAlign="start center" fxLayoutGap="20px">
<p>Zeige Warnung bis</p>
<mat-form-field>
<mat-label>Datum</mat-label>
<input matInput formControlName="globalWarningExpiredDay" [matDatepicker]="picker">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
</mat-form-field>
<mat-form-field>
<mat-select placeholder="Stunde" formControlName="globalWarningExpiredHour">
<mat-option *ngFor="let m of expiredHours | keyvalue" [value]="m.key">
{{m.value}}
</mat-option>
</mat-select>
</mat-form-field>
<p *ngIf="warningIsExpired" class="warning-warning">Zeitpunkt ist in der Vergangenheit.</p>
</div>
</div>
<mat-form-field fxLayout="column">
<mat-label>Name der Anwendung</mat-label>
<input matInput formControlName="appTitle" placeholder="Name">
</mat-form-field>
<mat-form-field fxLayout="column" fxLayoutAlign="start stretch">
<mat-label>Html-Inhalt für die Startseite rechts</mat-label>
<textarea matInput formControlName="introHtml"
cdkTextareaAutosize
cdkAutosizeMinRows="6"></textarea>
</mat-form-field>
<mat-form-field fxLayout="column" fxLayoutAlign="start stretch">
<mat-label>Html-Inhalt für die Impressum-/Datenschutzseite</mat-label>
<textarea matInput formControlName="imprintHtml"
cdkTextareaAutosize
cdkAutosizeMinRows="6"></textarea>
</mat-form-field>
<div fxLayout="row" fxLayoutGap="30px" fxLayoutAlign="start start">