From 4e051a1f40640f6cef62827261d9aac25845a5c7 Mon Sep 17 00:00:00 2001 From: Bastian Wagner Date: Fri, 20 Feb 2026 10:28:48 +0100 Subject: [PATCH] Logging und sowas --- api/src/model/entitites/user/user.entity.ts | 2 +- .../entitites/user/user.settings.entity.ts | 2 +- .../modules/system/dto/update-system.dto.ts | 4 ++- api/src/modules/system/system.controller.ts | 6 ++--- api/src/modules/system/system.module.ts | 3 ++- api/src/modules/system/system.service.ts | 25 +++++++++++++------ api/src/modules/user/user.service.ts | 4 +-- .../shared/service/activity.logger.service.ts | 11 ++++++++ .../create-cylinder.component.html | 2 ++ .../create-cylinder.component.ts | 12 ++++++++- .../modules/keys/create/create.component.html | 4 +-- .../modules/keys/create/create.component.ts | 3 ++- .../app/modules/system/system.component.ts | 13 ++++++++-- client/src/app/shared/api.service.ts | 18 +++++++++++++ 14 files changed, 87 insertions(+), 22 deletions(-) diff --git a/api/src/model/entitites/user/user.entity.ts b/api/src/model/entitites/user/user.entity.ts index cbd5472..0173964 100644 --- a/api/src/model/entitites/user/user.entity.ts +++ b/api/src/model/entitites/user/user.entity.ts @@ -58,7 +58,7 @@ export class User implements IUser { @DeleteDateColumn() deletedAt: Date; - @OneToOne(() => UserSettings, (settings) => settings.user, { cascade: true, onDelete: 'CASCADE', onUpdate: 'NO ACTION', }) + @OneToOne(() => UserSettings, (settings) => settings.user, { cascade: true, onUpdate: 'NO ACTION', }) settings: UserSettings; accessToken?: string; diff --git a/api/src/model/entitites/user/user.settings.entity.ts b/api/src/model/entitites/user/user.settings.entity.ts index a9f3b08..db26dde 100644 --- a/api/src/model/entitites/user/user.settings.entity.ts +++ b/api/src/model/entitites/user/user.settings.entity.ts @@ -6,7 +6,7 @@ export class UserSettings { @PrimaryGeneratedColumn('uuid') id: string; - @OneToOne(() => User, (user) => user.settings) + @OneToOne(() => User, (user) => user.settings, { onDelete: 'CASCADE' }) @JoinColumn() user: User; diff --git a/api/src/modules/system/dto/update-system.dto.ts b/api/src/modules/system/dto/update-system.dto.ts index 34a50a1..3dd0772 100644 --- a/api/src/modules/system/dto/update-system.dto.ts +++ b/api/src/modules/system/dto/update-system.dto.ts @@ -1,4 +1,6 @@ import { PartialType } from '@nestjs/mapped-types'; import { CreateSystemDto } from './create-system.dto'; -export class UpdateSystemDto extends PartialType(CreateSystemDto) {} +export class UpdateSystemDto extends PartialType(CreateSystemDto) { + id: string; +} diff --git a/api/src/modules/system/system.controller.ts b/api/src/modules/system/system.controller.ts index 8798906..816d5ca 100644 --- a/api/src/modules/system/system.controller.ts +++ b/api/src/modules/system/system.controller.ts @@ -48,9 +48,9 @@ export class SystemController { return this.systemService.findOne(id); } - @Put(':id') - update(@Param('id') id: string, @Body() updateSystemDto: UpdateSystemDto) { - return this.systemService.update(id, updateSystemDto); + @Put() + update(@Req() req: AuthenticatedRequest, @Body() updateSystemDto: UpdateSystemDto) { + return this.systemService.update(req.user, updateSystemDto); } @Delete(':id') diff --git a/api/src/modules/system/system.module.ts b/api/src/modules/system/system.module.ts index 0aaaebe..9940618 100644 --- a/api/src/modules/system/system.module.ts +++ b/api/src/modules/system/system.module.ts @@ -5,10 +5,11 @@ import { AuthModule } from '../auth/auth.module'; import { DatabaseModule } from 'src/shared/database/database.module'; import { MailModule } from '../mail/mail.module'; import { ConfigService } from '@nestjs/config'; +import { SharedServiceModule } from 'src/shared/service/shared.service.module'; @Module({ controllers: [SystemController], providers: [SystemService, ConfigService], - imports: [AuthModule, DatabaseModule, MailModule], + imports: [AuthModule, DatabaseModule, MailModule, SharedServiceModule], }) export class SystemModule {} diff --git a/api/src/modules/system/system.service.ts b/api/src/modules/system/system.service.ts index 8e826cc..ae1af35 100644 --- a/api/src/modules/system/system.service.ts +++ b/api/src/modules/system/system.service.ts @@ -6,6 +6,7 @@ import { User } from 'src/model/entitites'; import { IUser } from 'src/model/interface'; import { MailService } from '../mail/mail.service'; import { ConfigService } from '@nestjs/config'; +import { ActivityHelperService } from 'src/shared/service/activity.logger.service'; @Injectable() export class SystemService { @@ -14,7 +15,8 @@ export class SystemService { private userRepo: UserRepository, private systemActivityRepo: ActivityRepository, private mailService: MailService, - private readonly configService: ConfigService + private readonly configService: ConfigService, + private readonly activityService: ActivityHelperService ) {} get isDevelopMode(): boolean { @@ -59,12 +61,21 @@ export class SystemService { } // eslint-disable-next-line @typescript-eslint/no-unused-vars - update(id: string, updateSystemDto: UpdateSystemDto) { - throw new HttpException( - `This action updates a #${id} system but is not implemented`, - HttpStatus.NOT_IMPLEMENTED, - ); - return `This action updates a #${id} system`; + async update(user: User, updateSystemDto: UpdateSystemDto) { + if (!user || !user.id || !updateSystemDto.id) { throw new HttpException('forbidden', HttpStatus.FORBIDDEN); } + console.log(updateSystemDto); + const system = await this.systemRepo.findOne({ where: { id: updateSystemDto.id, managers: { id: user.id } }, withDeleted: true }); + + if (!system) { throw new HttpException('forbidden', HttpStatus.FORBIDDEN); } + + + if (system.name !== updateSystemDto.name) { + await this.activityService.logSystemRenamed({ system, newName: updateSystemDto.name, user }) + system.name = updateSystemDto.name; + + } + + return true; } async remove(id: string) { diff --git a/api/src/modules/user/user.service.ts b/api/src/modules/user/user.service.ts index a38c5d4..51d240d 100644 --- a/api/src/modules/user/user.service.ts +++ b/api/src/modules/user/user.service.ts @@ -13,8 +13,8 @@ export class UserService { private readonly systemActivityRepo: ActivityRepository, private readonly userSettingsRepository: UserSettingsRepository, private readonly helper: HelperService, - ) { - } + ) {} + getAllUsers(): Promise { diff --git a/api/src/shared/service/activity.logger.service.ts b/api/src/shared/service/activity.logger.service.ts index 3e99962..104f04e 100644 --- a/api/src/shared/service/activity.logger.service.ts +++ b/api/src/shared/service/activity.logger.service.ts @@ -14,6 +14,17 @@ export class ActivityHelperService { private readonly cylinderRepo: CylinderRepository, ) {} + async logSystemRenamed({system, newName, user}: { system: KeySystem, newName: string, user: User}) { + let msg = `Schließanlage von ${system.name} zu ${newName} umbenannt`; + + return this.activityRepo.save( + this.activityRepo.create({ + system, + user, + message: msg, + })) + } + async logDeleteKey(user: User, key: Key, system?: KeySystem) { if (!key || !user) { return; } diff --git a/client/src/app/modules/cylinder/components/create-cylinder/create-cylinder.component.html b/client/src/app/modules/cylinder/components/create-cylinder/create-cylinder.component.html index 6f30dc8..792cdbf 100644 --- a/client/src/app/modules/cylinder/components/create-cylinder/create-cylinder.component.html +++ b/client/src/app/modules/cylinder/components/create-cylinder/create-cylinder.component.html @@ -18,6 +18,7 @@ Zylinderlänge und co. + Schließanlage @@ -26,6 +27,7 @@ } Zu welcher Schließanlage gehört der Zylinder? + diff --git a/client/src/app/modules/cylinder/components/create-cylinder/create-cylinder.component.ts b/client/src/app/modules/cylinder/components/create-cylinder/create-cylinder.component.ts index 189a32c..3971823 100644 --- a/client/src/app/modules/cylinder/components/create-cylinder/create-cylinder.component.ts +++ b/client/src/app/modules/cylinder/components/create-cylinder/create-cylinder.component.ts @@ -9,10 +9,12 @@ import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { CommonModule } from '@angular/common'; @Component({ selector: 'app-create-cylinder', - imports: [MatFormFieldModule, MatInputModule, MatDialogModule, ReactiveFormsModule, FormsModule, MatSelectModule, MatButtonModule, MatIconModule], + imports: [MatFormFieldModule, MatInputModule, MatDialogModule, ReactiveFormsModule, FormsModule, MatSelectModule, MatButtonModule, MatIconModule, MatProgressBarModule, CommonModule], templateUrl: './create-cylinder.component.html', styleUrl: './create-cylinder.component.scss' }) @@ -22,6 +24,7 @@ export class CreateCylinderComponent { readonly dialogRef = inject(MatDialogRef); systems: any[] = []; + isLoading = true; createForm = new FormGroup({ name: new FormControl(null, Validators.required), @@ -35,6 +38,13 @@ export class CreateCylinderComponent { this.systems = systems; } }); + this.loadCylinders(); + } + + private async loadCylinders() { + this.isLoading = true; + await this.api.refreshSystems(); + this.isLoading = false; } save() { diff --git a/client/src/app/modules/keys/create/create.component.html b/client/src/app/modules/keys/create/create.component.html index 30975e1..a11f89d 100644 --- a/client/src/app/modules/keys/create/create.component.html +++ b/client/src/app/modules/keys/create/create.component.html @@ -29,12 +29,12 @@ Schließzylinder @for (item of cylinders; track $index) { - {{ item.name }} + {{ item.name }} ({{item.system.name}}) } Wo sperrt der Schlüssel? - diff --git a/client/src/app/modules/keys/create/create.component.ts b/client/src/app/modules/keys/create/create.component.ts index eb66256..e00c0fb 100644 --- a/client/src/app/modules/keys/create/create.component.ts +++ b/client/src/app/modules/keys/create/create.component.ts @@ -14,10 +14,11 @@ import { MatIconModule } from '@angular/material/icon'; import {MatCheckboxModule} from '@angular/material/checkbox'; import { IKey } from '../../../model/interface/key.interface'; import { ICylinder } from '../../../model/interface/cylinder.interface'; +import { MatTooltipModule } from '@angular/material/tooltip'; @Component({ selector: 'app-create', - imports: [MatDialogModule, MatButtonModule, ReactiveFormsModule, FormsModule, MatFormFieldModule, MatInputModule, MatSelectModule, MatDialogModule, MatIconModule, MatCheckboxModule], + imports: [MatDialogModule, MatButtonModule, ReactiveFormsModule, FormsModule, MatFormFieldModule, MatInputModule, MatSelectModule, MatDialogModule, MatIconModule, MatCheckboxModule, MatTooltipModule], templateUrl: './create.component.html', styleUrl: './create.component.scss' }) diff --git a/client/src/app/modules/system/system.component.ts b/client/src/app/modules/system/system.component.ts index f7ff89d..d41477c 100644 --- a/client/src/app/modules/system/system.component.ts +++ b/client/src/app/modules/system/system.component.ts @@ -1,7 +1,7 @@ import { DatePipe } from '@angular/common'; import { Component, inject, LOCALE_ID } from '@angular/core'; import { AgGridAngular } from 'ag-grid-angular'; -import { GridApi, GridOptions, GridReadyEvent } from 'ag-grid-community'; +import { CellEditingStoppedEvent, GridApi, GridOptions, GridReadyEvent } from 'ag-grid-community'; import { ApiService } from '../../shared/api.service'; import { HELPER } from '../../shared/helper.service'; import { MatButtonModule } from '@angular/material/button'; @@ -29,7 +29,7 @@ export class SystemComponent extends AgGridContainerComponent { constructor() { super(); this.gridOptions.columnDefs = [ - { colId: 'name', field: 'name', headerName: 'Name', sort: 'asc', flex: 1}, + { colId: 'name', field: 'name', headerName: 'Name', sort: 'asc', flex: 1, editable: true}, { colId: 'cylinderCount', field: 'cylinders', headerName: 'Zylinderanzahl', flex: 0, cellRenderer: (data: any) => data.value?.length || 0}, { field: 'createdAt', headerName: 'Angelegt', cellRenderer: (data: any) => data.value ? this.datePipe.transform(new Date(data.value)) : '-' }, { field: 'updatedAt', headerName: 'Upgedated', cellRenderer: (data: any) => data.value ? this.datePipe.transform(new Date(data.value)) : '-' }, @@ -51,6 +51,7 @@ export class SystemComponent extends AgGridContainerComponent { onGridReady(params: GridReadyEvent) { this.gridApi = params.api; + this.gridApi.addEventListener("cellEditingStopped", evt => this.cellEditEnd(evt)); this.api.systems.asObservable().subscribe({ next: systems => { this.gridApi.setGridOption("rowData", systems); @@ -60,6 +61,14 @@ export class SystemComponent extends AgGridContainerComponent { this.loadSystems(); } + async cellEditEnd(event: CellEditingStoppedEvent) { + const key: any = event.data; + if (!event.valueChanged || event.newValue == event.oldValue) { return; } + + this.api.updateSystem(key) + + } + openCreateSystem() { this.dialog.open(CreateSystemComponent).afterClosed().subscribe({ next: sys => { diff --git a/client/src/app/shared/api.service.ts b/client/src/app/shared/api.service.ts index f5c35f4..7e11182 100644 --- a/client/src/app/shared/api.service.ts +++ b/client/src/app/shared/api.service.ts @@ -92,6 +92,24 @@ export class ApiService { }) } + updateSystem(cylinder: any): Promise { + return new Promise(resolve => { + this.http.put('api/system', cylinder).pipe( + this.toast.observe({ + loading: `Schließanlage ${cylinder} wird gespeichert...`, + success: `Schließanlage ${cylinder.name} gespeichert.`, + error: 'Es ist ein Fehler aufgetreten' + }) + ).subscribe({ + next: () => resolve(true), + error: () => resolve(false), + complete: () => { + this.refreshCylinders() + } + }) + }) + } + createKey(key: any) { return this.http.post('api/key', key); }