From 62520466dc703a3dfa6bbcfe7468a1eb01140f72 Mon Sep 17 00:00:00 2001 From: Bastian Wagner Date: Fri, 20 Feb 2026 13:17:58 +0100 Subject: [PATCH] Impersination backend --- api/src/app.module.ts | 10 ++--- api/src/core/guards/auth.guard.ts | 2 +- .../model/entitites/impersination.entity.ts | 22 ++++++++++ .../repositories/impersination.repository.ts | 11 +++++ api/src/modules/auth/auth.controller.ts | 3 +- api/src/modules/auth/auth.service.ts | 19 +++++++- api/src/shared/database/database.module.ts | 6 ++- .../shared/service/system.helper.service.ts | 8 ++-- .../app/model/interface/customer.interface.ts | 6 +++ .../handover-dialog.component.ts | 25 +++++------ client/src/app/shared/api.service.ts | 43 +++++++++++++++---- 11 files changed, 118 insertions(+), 37 deletions(-) create mode 100644 api/src/model/entitites/impersination.entity.ts create mode 100644 api/src/model/repositories/impersination.repository.ts create mode 100644 client/src/app/model/interface/customer.interface.ts diff --git a/api/src/app.module.ts b/api/src/app.module.ts index 2c53611..54036ce 100644 --- a/api/src/app.module.ts +++ b/api/src/app.module.ts @@ -22,7 +22,7 @@ import { LogModule } from './modules/log/log.module'; envFilePath: ['.env'], isGlobal: true, }), - CacheModule.register({ ttl: 1000, isGlobal: true }), + // CacheModule.register({ ttl: 1000, isGlobal: true }), DatabaseModule, AuthModule, UserModule, @@ -38,10 +38,10 @@ import { LogModule } from './modules/log/log.module'; providers: [ AppService, AuthGuard, - { - provide: APP_INTERCEPTOR, - useClass: CacheInterceptor, - }, + // { + // provide: APP_INTERCEPTOR, + // useClass: CacheInterceptor, + // }, ], }) export class AppModule {} diff --git a/api/src/core/guards/auth.guard.ts b/api/src/core/guards/auth.guard.ts index 66c2a05..0257c94 100644 --- a/api/src/core/guards/auth.guard.ts +++ b/api/src/core/guards/auth.guard.ts @@ -35,7 +35,7 @@ export class AuthGuard implements CanActivate { if (payload.type != 'access') { throw new UnauthorizedException('wrong token'); } - const user = await this.authService.getUserById(payload.id); + const user = await this.authService.getUserById(payload.id, true); if (!user.isActive) { throw new HttpException('not active', HttpStatus.FORBIDDEN); } diff --git a/api/src/model/entitites/impersination.entity.ts b/api/src/model/entitites/impersination.entity.ts new file mode 100644 index 0000000..3e09a27 --- /dev/null +++ b/api/src/model/entitites/impersination.entity.ts @@ -0,0 +1,22 @@ +import { Entity, PrimaryGeneratedColumn, ManyToOne, JoinColumn, Column } from "typeorm"; +import { User } from "./user/user.entity"; + +@Entity() +export class Impersonation { + @PrimaryGeneratedColumn('uuid') + id: string; + + @ManyToOne(() => User, { nullable: false, eager: true }) + @JoinColumn({ name: 'fromUserId' }) + fromUser: User; + + @ManyToOne(() => User, { nullable: false, eager: true }) + @JoinColumn({ name: 'toUserId' }) + toUser: User; + + @Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP' }) + startedAt: Date; + + @Column({ type: 'datetime', nullable: true }) + endedAt?: Date; +} \ No newline at end of file diff --git a/api/src/model/repositories/impersination.repository.ts b/api/src/model/repositories/impersination.repository.ts new file mode 100644 index 0000000..37fa11d --- /dev/null +++ b/api/src/model/repositories/impersination.repository.ts @@ -0,0 +1,11 @@ +import { Injectable } from '@nestjs/common'; +import { Repository, DataSource } from 'typeorm'; +import { Impersonation } from '../entitites/impersination.entity'; + +@Injectable() +export class ImpersonationRepository extends Repository { + constructor(dataSource: DataSource) { + super(Impersonation, dataSource.createEntityManager()); + } + +} diff --git a/api/src/modules/auth/auth.controller.ts b/api/src/modules/auth/auth.controller.ts index d96c896..e468a0c 100644 --- a/api/src/modules/auth/auth.controller.ts +++ b/api/src/modules/auth/auth.controller.ts @@ -12,6 +12,7 @@ import { AuthService } from './auth.service'; import { AuthCodeDto } from 'src/model/dto'; import { User } from 'src/model/entitites'; import { AuthGuard } from 'src/core/guards/auth.guard'; +import { AuthenticatedRequest } from 'src/model/interface/authenticated-request.interface'; @Controller('auth') export class AuthController { @@ -30,7 +31,7 @@ export class AuthController { @UseGuards(AuthGuard) @Get('me') - getMe(@Req() req: any) { + getMe(@Req() req: AuthenticatedRequest) { return req.user; } diff --git a/api/src/modules/auth/auth.service.ts b/api/src/modules/auth/auth.service.ts index c9555ac..4d7d88e 100644 --- a/api/src/modules/auth/auth.service.ts +++ b/api/src/modules/auth/auth.service.ts @@ -8,11 +8,14 @@ import { JwtService } from '@nestjs/jwt'; import { IExternalAccessPayload, IPayload } from 'src/model/interface'; import { User } from 'src/model/entitites'; import { LogService, LogType } from '../log/log.service'; +import { ImpersonationRepository } from 'src/model/repositories/impersination.repository'; +import { IsNull } from 'typeorm'; @Injectable() export class AuthService { constructor( private userRepo: UserRepository, + private impersinationRepo: ImpersonationRepository, private readonly http: HttpService, private configService: ConfigService, private jwt: JwtService, @@ -119,9 +122,21 @@ export class AuthService { return bodyFormData; } - getUserById(id: string): Promise { + async getUserById(id: string, withImpersination = false): Promise { + this.log.log(LogType.Auth, null); - return this.userRepo.findById(id); + let user = await this.userRepo.findById(id); + if (withImpersination) { + const impersination = await this.impersinationRepo.findOne({ + where: { fromUser: { id: user.id }, endedAt: IsNull() }, + relations: ['toUser'] + }); + if (impersination) { + return this.userRepo.findById(impersination.toUser.id) + } + } + + return user; } async getNewToken(refresh: string) { diff --git a/api/src/shared/database/database.module.ts b/api/src/shared/database/database.module.ts index 471bd9c..220efbf 100644 --- a/api/src/shared/database/database.module.ts +++ b/api/src/shared/database/database.module.ts @@ -12,6 +12,7 @@ import { } from 'src/model/entitites'; import { EmailLog } from 'src/model/entitites/log'; import { KeySystem } from 'src/model/entitites/system.entity'; +import { Impersonation } from 'src/model/entitites/impersination.entity'; import { UserSettings } from 'src/model/entitites/user/user.settings.entity'; import { ActivityRepository, @@ -26,6 +27,7 @@ import { } from 'src/model/repositories'; import { KeyHandoutRepository } from 'src/model/repositories/key-handout.repository'; import { EmailLogRepository } from 'src/model/repositories/log'; +import { ImpersonationRepository } from 'src/model/repositories/impersination.repository'; const ENTITIES = [ User, @@ -39,6 +41,7 @@ const ENTITIES = [ Activity, EmailLog, UserSettings, + Impersonation ]; const REPOSITORIES = [ UserRepository, @@ -51,7 +54,8 @@ const REPOSITORIES = [ KeyHandoutRepository, ActivityRepository, EmailLogRepository, - UserSettingsRepository + UserSettingsRepository, + ImpersonationRepository ]; @Module({ diff --git a/api/src/shared/service/system.helper.service.ts b/api/src/shared/service/system.helper.service.ts index 67c0aab..951fa30 100644 --- a/api/src/shared/service/system.helper.service.ts +++ b/api/src/shared/service/system.helper.service.ts @@ -11,7 +11,7 @@ export class HelperService { private readonly systemRepository: KeySystemRepository, private readonly cylinderRepository: CylinderRepository, private readonly keyRepo: KeyRepository, - private cacheManager: Cache + // private cacheManager: Cache ) {} @@ -61,11 +61,11 @@ export class HelperService { } async cache() { - const value = await this.cacheManager.store.keys() - console.log(value) + // const value = await this.cacheManager.store.keys() + // console.log(value) } async deleteKeyArchiveCache() { - await this.cacheManager.del('/key/archive'); + // await this.cacheManager.del('/key/archive'); } } \ No newline at end of file diff --git a/client/src/app/model/interface/customer.interface.ts b/client/src/app/model/interface/customer.interface.ts new file mode 100644 index 0000000..c8f9d3e --- /dev/null +++ b/client/src/app/model/interface/customer.interface.ts @@ -0,0 +1,6 @@ +export interface ICustomer { + id: string; + name: string; + createdAt: string; + updatetAt: String; +} \ No newline at end of file diff --git a/client/src/app/modules/keys/components/handover-dialog/handover-dialog.component.ts b/client/src/app/modules/keys/components/handover-dialog/handover-dialog.component.ts index bab7d33..4d8739e 100644 --- a/client/src/app/modules/keys/components/handover-dialog/handover-dialog.component.ts +++ b/client/src/app/modules/keys/components/handover-dialog/handover-dialog.component.ts @@ -26,6 +26,7 @@ import { AG_GRID_LOCALE_DE } from '@ag-grid-community/locale'; import { AgGridAngular } from 'ag-grid-angular'; import { MatIconModule } from '@angular/material/icon'; import { AgGridContainerComponent } from '../../../../shared/ag-grid/components/ag-grid-container/ag-grid-container.component'; +import { ICustomer } from '../../../../model/interface/customer.interface'; @Component({ selector: 'app-handover-dialog', @@ -82,8 +83,8 @@ export class HandoverDialogComponent extends AgGridContainerComponent { isLoading: boolean = false; - customers: { name: string, id: string }[] = []; - filteredCustomers: Observable = new Observable(); + customers: ICustomer[] = []; + filteredCustomers: Observable = new Observable(); handoverForm = new FormGroup({ customer: new FormControl(null, Validators.required), @@ -125,18 +126,14 @@ export class HandoverDialogComponent extends AgGridContainerComponent { return promise; } - loadCustomers() { - - - return new Promise(async resolve => { - const customers = await this.api.getCustomers(); - this.customers = customers; - this.filteredCustomers = this.handoverForm.controls.customer.valueChanges.pipe( - startWith(''), - map(value => this._filter(value || '')), - ); - resolve(customers) - }); + async loadCustomers() { + const customers = await this.api.refreshCustomers() + this.customers = customers; + this.filteredCustomers = this.handoverForm.controls.customer.valueChanges.pipe( + startWith(''), + map(value => this._filter(value || '')), + ); + return Promise.resolve() } private _filter(value: string): any[] { diff --git a/client/src/app/shared/api.service.ts b/client/src/app/shared/api.service.ts index 4161eea..1e06b33 100644 --- a/client/src/app/shared/api.service.ts +++ b/client/src/app/shared/api.service.ts @@ -5,6 +5,7 @@ import { IUser } from '../model/interface/user.interface'; import { IKey } from '../model/interface/key.interface'; import { ICylinder } from '../model/interface/cylinder.interface'; import { HotToastService } from '@ngxpert/hot-toast'; +import { ICustomer } from '../model/interface/customer.interface'; @Injectable({ providedIn: 'root' @@ -16,6 +17,7 @@ export class ApiService { public keys: BehaviorSubject = new BehaviorSubject([]); public cylinders: BehaviorSubject = new BehaviorSubject([]); public systems: BehaviorSubject = new BehaviorSubject([]); + public customers: BehaviorSubject = new BehaviorSubject([]); public user: BehaviorSubject = new BehaviorSubject(null!); @@ -55,6 +57,9 @@ export class ApiService { return this.http.get('api/key') } + /** + * triggert das Laden der Schlüssel vom Server und speichert sie in keys + */ refreshKeys(): void{ this.getKeys().subscribe({ next: keys => { @@ -138,16 +143,25 @@ export class ApiService { return this.http.post('api/customer', data); } - getCustomers(): Promise { - return new Promise(resolve => { - this.http.get('api/customer').subscribe({ - next: (customers) => resolve(customers), - error: (err) => { - this.toast.error('Fehler beim Laden der Mieter'); - resolve([]); - } + private getCustomers(): Observable { + return this.http.get('api/customer') + } - }) + /** + * triggert das Laden der Schlüssel vom Server und speichert sie in keys + */ + refreshCustomers(): Promise { + return new Promise(resolve => { + this.getCustomers().subscribe({ + next: customers => { + this.customers.next(customers); + resolve(customers) + }, + error: () => { + this.toast.error('Fehler beim Laden der Schlüssel'); + resolve([]) + } + }) }) } @@ -176,6 +190,12 @@ export class ApiService { }) } + /** + * Löscht das System vom Server und zeigt Toast an. + * Aktualisiert die Systeme danach + * @param system zu löschen + * @returns true oder false + */ deleteSystem(system: any): Promise { return new Promise(resolve => { this.http.delete(`api/system${system.id}`).pipe( @@ -239,6 +259,11 @@ export class ApiService { }) } + /** + * Aktualisiert die Schließanlagen im Behaviour Subject + * systems + * @returns Promise wenn geladen + */ refreshSystems(): Promise { return new Promise(resolve => { this.getSystems().subscribe({