Notifications

This commit is contained in:
Bastian Wagner
2025-01-20 15:15:41 +01:00
parent adca81444e
commit 2674ec0d24
25 changed files with 126 additions and 35 deletions

View File

@@ -4,7 +4,7 @@ import { IUser } from "src/model/interface";
export class MockUserRepository { export class MockUserRepository {
user: User = { user: any = {
firstName: null, firstName: null,
lastName: null, lastName: null,
id: 'mockId', id: 'mockId',
@@ -20,7 +20,11 @@ export class MockUserRepository {
isActive: false, isActive: false,
role: null, role: null,
systems: [], systems: [],
deletedAt: null deletedAt: null,
settings: {
id: 'id',
user: null
}
} }
findByUsername = jest.fn().mockImplementation((username: string) => { findByUsername = jest.fn().mockImplementation((username: string) => {
@@ -28,7 +32,7 @@ export class MockUserRepository {
}) })
createUser = jest.fn().mockImplementation((register: CreateUserDto) => { createUser = jest.fn().mockImplementation((register: CreateUserDto) => {
const user: User = { const user: any = {
firstName: null, firstName: null,
lastName: null, lastName: null,
id: 'mockId', id: 'mockId',
@@ -44,7 +48,11 @@ export class MockUserRepository {
isActive: false, isActive: false,
role: null, role: null,
systems: [], systems: [],
deletedAt: null deletedAt: null,
settings: {
id: 'id',
user: null
}
} }
return user; return user;
}) })

View File

@@ -6,8 +6,8 @@ import {
OneToMany, OneToMany,
PrimaryGeneratedColumn, PrimaryGeneratedColumn,
} from 'typeorm'; } from 'typeorm';
import { User } from './user.entity';
import { KeySystem } from './system.entity'; import { KeySystem } from './system.entity';
import { User } from './user';
@Entity() @Entity()
export class Activity { export class Activity {

View File

@@ -1,8 +1,7 @@
export * from './sso.user.entity';
export * from './user.entity';
export * from './role.entity'; export * from './role.entity';
export * from './cylinder.entity'; export * from './cylinder.entity';
export * from './key.entity'; export * from './key.entity';
export * from './customer.entity'; export * from './customer.entity';
export * from './key-handout.entity'; export * from './key-handout.entity';
export * from './activity.entity'; export * from './activity.entity';
export * from './user';

View File

@@ -8,7 +8,7 @@ import {
} from 'typeorm'; } from 'typeorm';
import { Key } from './key.entity'; import { Key } from './key.entity';
import { Customer } from './customer.entity'; import { Customer } from './customer.entity';
import { User } from './user.entity'; import { User } from './user';
@Entity() @Entity()
export class KeyHandout { export class KeyHandout {

View File

@@ -1,4 +1,5 @@
import { import {
AfterLoad,
Column, Column,
CreateDateColumn, CreateDateColumn,
Entity, Entity,
@@ -25,9 +26,16 @@ export class EmailLog {
@Column({ name: 'type', type: 'text',}) @Column({ name: 'type', type: 'text',})
type: EmailEvent; type: EmailEvent;
eventName: string;
@ManyToOne(() => KeySystem, { nullable: true }) @ManyToOne(() => KeySystem, { nullable: true })
system: KeySystem; system: KeySystem;
@Column({type: 'boolean'}) @Column({type: 'boolean'})
success: boolean; success: boolean;
@AfterLoad()
setType() {
this.eventName = EmailEvent[this.type]
}
} }

View File

@@ -4,7 +4,7 @@ import {
OneToMany, OneToMany,
PrimaryGeneratedColumn, PrimaryGeneratedColumn,
} from 'typeorm'; } from 'typeorm';
import { User } from './user.entity'; import { User } from './user';
@Entity() @Entity()
export class Role { export class Role {

View File

@@ -9,8 +9,8 @@ import {
UpdateDateColumn, UpdateDateColumn,
} from 'typeorm'; } from 'typeorm';
import { IKeySystem } from '../interface'; import { IKeySystem } from '../interface';
import { User } from './user.entity';
import { Cylinder } from './cylinder.entity'; import { Cylinder } from './cylinder.entity';
import { User } from './user';
@Entity() @Entity()
export class KeySystem implements IKeySystem { export class KeySystem implements IKeySystem {

View File

@@ -0,0 +1,2 @@
export * from './sso.user.entity';
export * from './user.entity';

View File

@@ -1,5 +1,9 @@
import { Exclude, Transform } from 'class-transformer'; import { Exclude, Transform } from 'class-transformer';
import { IsEmail } from 'class-validator';
import { IUser } from 'src/model/interface';
import { import {
BeforeInsert,
BeforeUpdate,
Column, Column,
CreateDateColumn, CreateDateColumn,
DeleteDateColumn, DeleteDateColumn,
@@ -10,11 +14,10 @@ import {
OneToOne, OneToOne,
PrimaryGeneratedColumn, PrimaryGeneratedColumn,
} from 'typeorm'; } from 'typeorm';
import { IUser } from '../interface'; import { Role } from '../role.entity';
import { KeySystem } from '../system.entity';
import { SSOUser } from './sso.user.entity'; import { SSOUser } from './sso.user.entity';
import { IsEmail } from 'class-validator'; import { UserSettings } from './user.settings.entity';
import { Role } from './role.entity';
import { KeySystem } from './system.entity';
@Entity() @Entity()
export class User implements IUser { export class User implements IUser {
@@ -55,6 +58,18 @@ export class User implements IUser {
@DeleteDateColumn() @DeleteDateColumn()
deletedAt: Date; deletedAt: Date;
@OneToOne(() => UserSettings, (settings) => settings.user, { cascade: true })
settings: UserSettings;
accessToken?: string; accessToken?: string;
refreshToken?: string; refreshToken?: string;
@BeforeInsert()
@BeforeUpdate()
createSettings() {
if (this.settings == null) {
this.settings = {} as any;
}
}
} }

View File

@@ -0,0 +1,25 @@
import { Column, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn } from "typeorm";
import { User } from "./user.entity";
@Entity()
export class UserSettings {
@PrimaryGeneratedColumn('uuid')
id: string;
@OneToOne(() => User, (user) => user.settings)
@JoinColumn()
user: User;
@Column({ name: 'send_system_access_notification', default: true, type: 'boolean'})
sendSystemAccessMails: boolean;
@Column({ name: 'send_user_disabled_notification', default: true, type: 'boolean'})
sendUserDisabledMails: boolean;
@Column({ name: 'send_system_update_notification', default: true, type: 'boolean'})
sendSystemUpdateMails: boolean;
}

View File

@@ -1,4 +1,5 @@
import { Role } from '../entitites'; import { Role } from '../entitites';
import { UserSettings } from '../entitites/user/user.settings.entity';
export interface IUser { export interface IUser {
id: string; id: string;
@@ -13,4 +14,6 @@ export interface IUser {
deletedAt?: Date; deletedAt?: Date;
role?: string | Role; role?: string | Role;
settings?: UserSettings;
} }

View File

@@ -6,3 +6,4 @@ export * from './cylinder.repository';
export * from './key.repository'; export * from './key.repository';
export * from './customer.repository'; export * from './customer.repository';
export * from './activity.repository'; export * from './activity.repository';
export * from './user.settings.repository';

View File

@@ -0,0 +1,10 @@
import { Injectable } from '@nestjs/common';
import { Repository, DataSource } from 'typeorm';
import { UserSettings } from '../entitites/user/user.settings.entity';
@Injectable()
export class UserSettingsRepository extends Repository<UserSettings> {
constructor(dataSource: DataSource) {
super(UserSettings, dataSource.createEntityManager());
}
}

View File

@@ -3,7 +3,8 @@ import { EmailLogRepository } from 'src/model/repositories/log';
@Injectable() @Injectable()
export class LogService { export class LogService {
constructor(private readonly emailLogRepo: EmailLogRepository) {} constructor(private readonly emailLogRepo: EmailLogRepository) {
}
@@ -16,7 +17,6 @@ export class LogService {
private async logEmail(data: EmailLogDto) { private async logEmail(data: EmailLogDto) {
const log = this.emailLogRepo.create(data); const log = this.emailLogRepo.create(data);
const logEntry = await this.emailLogRepo.save(log); const logEntry = await this.emailLogRepo.save(log);
console.log(logEntry);
} }
} }

View File

@@ -40,6 +40,5 @@ import { LogModule } from '../log/log.module';
}) })
export class MailModule { export class MailModule {
constructor() { constructor() {
console.log(join(__dirname, '../../../templates'))
} }
} }

View File

@@ -82,7 +82,13 @@ export class SystemService {
sys.managers = sys.managers.filter( m => m.username != manageObject.email); sys.managers = sys.managers.filter( m => m.username != manageObject.email);
await this.systemRepo.save(sys); await this.systemRepo.save(sys);
this.mailService.sendAccessRemovedMail({to: manageObject.email, firstName: manageObject.email, systemName: sys.name}) const user = await this.userRepo.findOne({
where: { username: manageObject.email.trim() },
relations: ['settings']
});
if (user.settings.sendSystemAccessMails) {
this.mailService.sendAccessRemovedMail({to: manageObject.email, firstName: manageObject.email, systemName: sys.name})
}
return sys.managers; return sys.managers;
} }
@@ -90,14 +96,19 @@ export class SystemService {
return sys.managers; return sys.managers;
} }
const user = await this.userRepo.findOneBy({ username: manageObject.email.trim() }); const user = await this.userRepo.findOne({
where: { username: manageObject.email.trim() },
relations: ['settings']
});
if (!user) { if (!user) {
throw new HttpException('Es wurde kein User mit dieser Emailadresse gefunden. Bitte prüfe die Emailadresse und versuche es erneut.', HttpStatus.NOT_FOUND); throw new HttpException('Es wurde kein User mit dieser Emailadresse gefunden. Bitte prüfe die Emailadresse und versuche es erneut.', HttpStatus.NOT_FOUND);
} }
sys.managers.push(user); sys.managers.push(user);
await this.systemRepo.save(sys); await this.systemRepo.save(sys);
this.mailService.sendAccessGrantedMail({to: user.username, firstName: user.firstName, systemName: sys.name}) if (user.settings.sendSystemAccessMails) {
this.mailService.sendAccessGrantedMail({to: user.username, firstName: user.firstName, systemName: sys.name})
}
return sys.managers; return sys.managers;
} }

View File

@@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { User } from 'src/model/entitites'; import { User } from 'src/model/entitites';
import { IUser } from 'src/model/interface'; import { IUser } from 'src/model/interface';
import { ActivityRepository, KeySystemRepository, RoleRepository, UserRepository } from 'src/model/repositories'; import { ActivityRepository, KeySystemRepository, RoleRepository, UserRepository, UserSettingsRepository } from 'src/model/repositories';
import { HelperService } from 'src/shared/service/system.helper.service'; import { HelperService } from 'src/shared/service/system.helper.service';
@Injectable() @Injectable()
export class UserService { export class UserService {
@@ -10,12 +10,15 @@ export class UserService {
private readonly roleRepo: RoleRepository, private readonly roleRepo: RoleRepository,
private readonly systemRepo: KeySystemRepository, private readonly systemRepo: KeySystemRepository,
private readonly systemActivityRepo: ActivityRepository, private readonly systemActivityRepo: ActivityRepository,
private readonly userSettingsRepository: UserSettingsRepository,
private readonly helper: HelperService, private readonly helper: HelperService,
) {} ) {
}
getAllUsers(): Promise<User[]> { getAllUsers(): Promise<User[]> {
return this.userRepo.find({ return this.userRepo.find({
relations: ['role'], relations: ['role', 'settings'],
where: [{ role: { name: 'user' } }, { role: { name: 'admin' } }], where: [{ role: { name: 'user' } }, { role: { name: 'admin' } }],
}); });
} }

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 { UserSettings } from 'src/model/entitites/user/user.settings.entity';
import { import {
ActivityRepository, ActivityRepository,
CustomerRepository, CustomerRepository,
@@ -21,6 +22,7 @@ import {
RoleRepository, RoleRepository,
SsoUserRepository, SsoUserRepository,
UserRepository, UserRepository,
UserSettingsRepository,
} 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';
@@ -36,6 +38,7 @@ const ENTITIES = [
KeyHandout, KeyHandout,
Activity, Activity,
EmailLog, EmailLog,
UserSettings,
]; ];
const REPOSITORIES = [ const REPOSITORIES = [
UserRepository, UserRepository,
@@ -47,7 +50,8 @@ const REPOSITORIES = [
CustomerRepository, CustomerRepository,
KeyHandoutRepository, KeyHandoutRepository,
ActivityRepository, ActivityRepository,
EmailLogRepository EmailLogRepository,
UserSettingsRepository
]; ];
@Module({ @Module({

View File

@@ -1,10 +1,7 @@
import { Module } from "@nestjs/common"; import { Module } from "@nestjs/common";
import { DatabaseModule } from "../database/database.module"; import { DatabaseModule } from "../database/database.module";
import { HelperService } from "./system.helper.service"; import { HelperService } from "./system.helper.service";
import { CylinderRepository, KeySystemRepository } from "src/model/repositories";
import { Cylinder, User } from "src/model/entitites";
import { ActivityHelperService } from "./activity.logger.service"; import { ActivityHelperService } from "./activity.logger.service";
import { CacheModule } from "@nestjs/cache-manager";
@Module({ @Module({
imports: [ DatabaseModule ], imports: [ DatabaseModule ],

View File

@@ -83,11 +83,11 @@
<p>Hallo {{firstName}},</p> <p>Hallo {{firstName}},</p>
<p>Du wurdest als Verwalter folgender Schließanlage entfernt:</p> <p>Du wurdest als Verwalter folgender Schließanlage entfernt:</p>
<div class="button-container"> <div class="button-container">
<a href="#" class="btn">{{systemName}}</a> <a href="https://keyvaultpro.de/systems" class="btn">{{systemName}}</a>
</div> </div>
</div> </div>
<div class="footer"> <div class="footer">
<p>Falls du Fragen hast, kontaktiere uns bitte <a href="mailto:support@example.com">hier</a>.</p>
</div> </div>
</div> </div>
</body> </body>

View File

@@ -83,11 +83,11 @@
<p>Hallo {{firstName}},</p> <p>Hallo {{firstName}},</p>
<p>Du wurdest als Verwalter zu folgender Schließanlage hinzugefügt:</p> <p>Du wurdest als Verwalter zu folgender Schließanlage hinzugefügt:</p>
<div class="button-container"> <div class="button-container">
<a href="#" class="btn">{{systemName}}</a> <a href="https://keyvaultpro.de/systems" class="btn">{{systemName}}</a>
</div> </div>
</div> </div>
<div class="footer"> <div class="footer">
<p>Falls du Fragen hast, kontaktiere uns bitte <a href="mailto:support@example.com">hier</a>.</p>
</div> </div>
</div> </div>
</body> </body>

View File

@@ -74,7 +74,16 @@ export class AllUsersComponent {
this.deleteUser(event.data.id); this.deleteUser(event.data.id);
} }
}, },
} },
{
headerName: 'Benachrichtigungen',
children: [
{ columnGroupShow: "closed", width: 180 , cellRenderer: 'agCheckboxCellRenderer', valueGetter: (data: any) => { return Object.values(data.data.settings).filter(v => typeof v == 'boolean').some((x: any) => x)}, type: 'boolean' },
{ field: 'settings.sendSystemAccessMails', headerName: 'Schlüssesystemzugriff', editable: true, columnGroupShow: "open" },
{ field: 'settings.sendSystemUpdateMails', headerName: 'Schließsystemupdates', editable: true, columnGroupShow: "open" },
{ field: 'settings.sendUserDisabledMails', headerName: 'User deaktiviert', editable: true, columnGroupShow: "open" }
]
},
], ],
loading: true, loading: true,
overlayLoadingTemplate: 'Lade Daten...' overlayLoadingTemplate: 'Lade Daten...'

View File

@@ -62,7 +62,6 @@ export class CreateKeyComponent {
} }
save() { save() {
console.log(this.createForm.value)
this.api.createKey(this.createForm.value as any) this.api.createKey(this.createForm.value as any)
.pipe( .pipe(
@@ -93,7 +92,6 @@ export class CreateKeyComponent {
next: c => { next: c => {
if (c) { if (c) {
this.createForm.controls.cylinder.patchValue(c); this.createForm.controls.cylinder.patchValue(c);
console.log(c);
} }
} }
}) })

View File

@@ -195,7 +195,6 @@ export class KeysComponent {
disableClose: true disableClose: true
}).afterClosed().subscribe({ }).afterClosed().subscribe({
next: changed => { next: changed => {
console.log(changed)
if (changed) { if (changed) {
this.loadKeys(); this.loadKeys();
} }