Impersination backend

This commit is contained in:
Bastian Wagner
2026-02-20 13:17:58 +01:00
parent affea90e91
commit 62520466dc
11 changed files with 118 additions and 37 deletions

View File

@@ -22,7 +22,7 @@ import { LogModule } from './modules/log/log.module';
envFilePath: ['.env'], envFilePath: ['.env'],
isGlobal: true, isGlobal: true,
}), }),
CacheModule.register({ ttl: 1000, isGlobal: true }), // CacheModule.register({ ttl: 1000, isGlobal: true }),
DatabaseModule, DatabaseModule,
AuthModule, AuthModule,
UserModule, UserModule,
@@ -38,10 +38,10 @@ import { LogModule } from './modules/log/log.module';
providers: [ providers: [
AppService, AppService,
AuthGuard, AuthGuard,
{ // {
provide: APP_INTERCEPTOR, // provide: APP_INTERCEPTOR,
useClass: CacheInterceptor, // useClass: CacheInterceptor,
}, // },
], ],
}) })
export class AppModule {} export class AppModule {}

View File

@@ -35,7 +35,7 @@ export class AuthGuard implements CanActivate {
if (payload.type != 'access') { if (payload.type != 'access') {
throw new UnauthorizedException('wrong token'); 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) { if (!user.isActive) {
throw new HttpException('not active', HttpStatus.FORBIDDEN); throw new HttpException('not active', HttpStatus.FORBIDDEN);
} }

View File

@@ -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;
}

View File

@@ -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<Impersonation> {
constructor(dataSource: DataSource) {
super(Impersonation, dataSource.createEntityManager());
}
}

View File

@@ -12,6 +12,7 @@ import { AuthService } from './auth.service';
import { AuthCodeDto } from 'src/model/dto'; import { AuthCodeDto } from 'src/model/dto';
import { User } from 'src/model/entitites'; import { User } from 'src/model/entitites';
import { AuthGuard } from 'src/core/guards/auth.guard'; import { AuthGuard } from 'src/core/guards/auth.guard';
import { AuthenticatedRequest } from 'src/model/interface/authenticated-request.interface';
@Controller('auth') @Controller('auth')
export class AuthController { export class AuthController {
@@ -30,7 +31,7 @@ export class AuthController {
@UseGuards(AuthGuard) @UseGuards(AuthGuard)
@Get('me') @Get('me')
getMe(@Req() req: any) { getMe(@Req() req: AuthenticatedRequest) {
return req.user; return req.user;
} }

View File

@@ -8,11 +8,14 @@ import { JwtService } from '@nestjs/jwt';
import { IExternalAccessPayload, IPayload } from 'src/model/interface'; import { IExternalAccessPayload, IPayload } from 'src/model/interface';
import { User } from 'src/model/entitites'; import { User } from 'src/model/entitites';
import { LogService, LogType } from '../log/log.service'; import { LogService, LogType } from '../log/log.service';
import { ImpersonationRepository } from 'src/model/repositories/impersination.repository';
import { IsNull } from 'typeorm';
@Injectable() @Injectable()
export class AuthService { export class AuthService {
constructor( constructor(
private userRepo: UserRepository, private userRepo: UserRepository,
private impersinationRepo: ImpersonationRepository,
private readonly http: HttpService, private readonly http: HttpService,
private configService: ConfigService, private configService: ConfigService,
private jwt: JwtService, private jwt: JwtService,
@@ -119,9 +122,21 @@ export class AuthService {
return bodyFormData; return bodyFormData;
} }
getUserById(id: string): Promise<User> { async getUserById(id: string, withImpersination = false): Promise<User> {
this.log.log(LogType.Auth, null); 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) { async getNewToken(refresh: string) {

View File

@@ -12,6 +12,7 @@ import {
} from 'src/model/entitites'; } from 'src/model/entitites';
import { EmailLog } from 'src/model/entitites/log'; import { EmailLog } from 'src/model/entitites/log';
import { KeySystem } from 'src/model/entitites/system.entity'; 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 { UserSettings } from 'src/model/entitites/user/user.settings.entity';
import { import {
ActivityRepository, ActivityRepository,
@@ -26,6 +27,7 @@ import {
} from 'src/model/repositories'; } from 'src/model/repositories';
import { KeyHandoutRepository } from 'src/model/repositories/key-handout.repository'; import { KeyHandoutRepository } from 'src/model/repositories/key-handout.repository';
import { EmailLogRepository } from 'src/model/repositories/log'; import { EmailLogRepository } from 'src/model/repositories/log';
import { ImpersonationRepository } from 'src/model/repositories/impersination.repository';
const ENTITIES = [ const ENTITIES = [
User, User,
@@ -39,6 +41,7 @@ const ENTITIES = [
Activity, Activity,
EmailLog, EmailLog,
UserSettings, UserSettings,
Impersonation
]; ];
const REPOSITORIES = [ const REPOSITORIES = [
UserRepository, UserRepository,
@@ -51,7 +54,8 @@ const REPOSITORIES = [
KeyHandoutRepository, KeyHandoutRepository,
ActivityRepository, ActivityRepository,
EmailLogRepository, EmailLogRepository,
UserSettingsRepository UserSettingsRepository,
ImpersonationRepository
]; ];
@Module({ @Module({

View File

@@ -11,7 +11,7 @@ export class HelperService {
private readonly systemRepository: KeySystemRepository, private readonly systemRepository: KeySystemRepository,
private readonly cylinderRepository: CylinderRepository, private readonly cylinderRepository: CylinderRepository,
private readonly keyRepo: KeyRepository, private readonly keyRepo: KeyRepository,
private cacheManager: Cache // private cacheManager: Cache
) {} ) {}
@@ -61,11 +61,11 @@ export class HelperService {
} }
async cache() { async cache() {
const value = await this.cacheManager.store.keys() // const value = await this.cacheManager.store.keys()
console.log(value) // console.log(value)
} }
async deleteKeyArchiveCache() { async deleteKeyArchiveCache() {
await this.cacheManager.del('/key/archive'); // await this.cacheManager.del('/key/archive');
} }
} }

View File

@@ -0,0 +1,6 @@
export interface ICustomer {
id: string;
name: string;
createdAt: string;
updatetAt: String;
}

View File

@@ -26,6 +26,7 @@ import { AG_GRID_LOCALE_DE } from '@ag-grid-community/locale';
import { AgGridAngular } from 'ag-grid-angular'; import { AgGridAngular } from 'ag-grid-angular';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { AgGridContainerComponent } from '../../../../shared/ag-grid/components/ag-grid-container/ag-grid-container.component'; import { AgGridContainerComponent } from '../../../../shared/ag-grid/components/ag-grid-container/ag-grid-container.component';
import { ICustomer } from '../../../../model/interface/customer.interface';
@Component({ @Component({
selector: 'app-handover-dialog', selector: 'app-handover-dialog',
@@ -82,8 +83,8 @@ export class HandoverDialogComponent extends AgGridContainerComponent {
isLoading: boolean = false; isLoading: boolean = false;
customers: { name: string, id: string }[] = []; customers: ICustomer[] = [];
filteredCustomers: Observable<any[]> = new Observable(); filteredCustomers: Observable<ICustomer[]> = new Observable();
handoverForm = new FormGroup({ handoverForm = new FormGroup({
customer: new FormControl<any>(null, Validators.required), customer: new FormControl<any>(null, Validators.required),
@@ -125,18 +126,14 @@ export class HandoverDialogComponent extends AgGridContainerComponent {
return promise; return promise;
} }
loadCustomers() { async loadCustomers() {
const customers = await this.api.refreshCustomers()
return new Promise(async resolve => {
const customers = await this.api.getCustomers();
this.customers = customers; this.customers = customers;
this.filteredCustomers = this.handoverForm.controls.customer.valueChanges.pipe( this.filteredCustomers = this.handoverForm.controls.customer.valueChanges.pipe(
startWith(''), startWith(''),
map(value => this._filter(value || '')), map(value => this._filter(value || '')),
); );
resolve(customers) return Promise.resolve()
});
} }
private _filter(value: string): any[] { private _filter(value: string): any[] {

View File

@@ -5,6 +5,7 @@ import { IUser } from '../model/interface/user.interface';
import { IKey } from '../model/interface/key.interface'; import { IKey } from '../model/interface/key.interface';
import { ICylinder } from '../model/interface/cylinder.interface'; import { ICylinder } from '../model/interface/cylinder.interface';
import { HotToastService } from '@ngxpert/hot-toast'; import { HotToastService } from '@ngxpert/hot-toast';
import { ICustomer } from '../model/interface/customer.interface';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@@ -16,6 +17,7 @@ export class ApiService {
public keys: BehaviorSubject<IKey[]> = new BehaviorSubject<IKey[]>([]); public keys: BehaviorSubject<IKey[]> = new BehaviorSubject<IKey[]>([]);
public cylinders: BehaviorSubject<ICylinder[]> = new BehaviorSubject<ICylinder[]>([]); public cylinders: BehaviorSubject<ICylinder[]> = new BehaviorSubject<ICylinder[]>([]);
public systems: BehaviorSubject<any[]> = new BehaviorSubject<any[]>([]); public systems: BehaviorSubject<any[]> = new BehaviorSubject<any[]>([]);
public customers: BehaviorSubject<ICustomer[]> = new BehaviorSubject<ICustomer[]>([]);
public user: BehaviorSubject<IUser> = new BehaviorSubject<IUser>(null!); public user: BehaviorSubject<IUser> = new BehaviorSubject<IUser>(null!);
@@ -55,6 +57,9 @@ export class ApiService {
return this.http.get<IKey[]>('api/key') return this.http.get<IKey[]>('api/key')
} }
/**
* triggert das Laden der Schlüssel vom Server und speichert sie in keys
*/
refreshKeys(): void{ refreshKeys(): void{
this.getKeys().subscribe({ this.getKeys().subscribe({
next: keys => { next: keys => {
@@ -138,15 +143,24 @@ export class ApiService {
return this.http.post('api/customer', data); return this.http.post('api/customer', data);
} }
getCustomers(): Promise<any[]> { private getCustomers(): Observable<ICustomer[]> {
return new Promise(resolve => { return this.http.get<ICustomer[]>('api/customer')
this.http.get<any[]>('api/customer').subscribe({
next: (customers) => resolve(customers),
error: (err) => {
this.toast.error('Fehler beim Laden der Mieter');
resolve([]);
} }
/**
* triggert das Laden der Schlüssel vom Server und speichert sie in keys
*/
refreshCustomers(): Promise<ICustomer[]> {
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<boolean> { deleteSystem(system: any): Promise<boolean> {
return new Promise<boolean>(resolve => { return new Promise<boolean>(resolve => {
this.http.delete(`api/system${system.id}`).pipe( 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<void> { refreshSystems(): Promise<void> {
return new Promise(resolve => { return new Promise(resolve => {
this.getSystems().subscribe({ this.getSystems().subscribe({