Mailservice
This commit is contained in:
@@ -16,4 +16,13 @@ SSO_CLIENT_ID=
|
|||||||
|
|
||||||
# SECURITY
|
# SECURITY
|
||||||
JWT_SECRET=
|
JWT_SECRET=
|
||||||
JWT_EXPIRES_IN=10m
|
JWT_EXPIRES_IN=10m
|
||||||
|
|
||||||
|
|
||||||
|
# Mail
|
||||||
|
MAILER_HOST=
|
||||||
|
MAILER_PORT=
|
||||||
|
MAILER_SECURE=
|
||||||
|
MAILER_USERNAME=
|
||||||
|
MAILER_PASSWORD=
|
||||||
|
MAILER_FROM=''
|
||||||
@@ -3,6 +3,9 @@
|
|||||||
"collection": "@nestjs/schematics",
|
"collection": "@nestjs/schematics",
|
||||||
"sourceRoot": "src",
|
"sourceRoot": "src",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"deleteOutDir": true
|
"deleteOutDir": true,
|
||||||
|
"assets": [
|
||||||
|
{ "include": "./templates/**", "outDir": "dist", "watchAssets": true }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2695
api/package-lock.json
generated
2695
api/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -20,6 +20,7 @@
|
|||||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand"
|
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@nestjs-modules/mailer": "^2.0.2",
|
||||||
"@nestjs/axios": "^3.0.3",
|
"@nestjs/axios": "^3.0.3",
|
||||||
"@nestjs/cache-manager": "^2.3.0",
|
"@nestjs/cache-manager": "^2.3.0",
|
||||||
"@nestjs/common": "^10.0.0",
|
"@nestjs/common": "^10.0.0",
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import { CylinderModule } from './modules/cylinder/cylinder.module';
|
|||||||
import { SystemModule } from './modules/system/system.module';
|
import { SystemModule } from './modules/system/system.module';
|
||||||
import { CacheInterceptor, CacheModule } from '@nestjs/cache-manager';
|
import { CacheInterceptor, CacheModule } from '@nestjs/cache-manager';
|
||||||
import { APP_INTERCEPTOR } from '@nestjs/core';
|
import { APP_INTERCEPTOR } from '@nestjs/core';
|
||||||
|
import { MailModule } from './modules/mail/mail.module';
|
||||||
|
import { LogModule } from './modules/log/log.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -29,6 +31,8 @@ import { APP_INTERCEPTOR } from '@nestjs/core';
|
|||||||
CustomerModule,
|
CustomerModule,
|
||||||
CylinderModule,
|
CylinderModule,
|
||||||
SystemModule,
|
SystemModule,
|
||||||
|
MailModule,
|
||||||
|
LogModule,
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [
|
providers: [
|
||||||
|
|||||||
33
api/src/model/entitites/log/email.log.entity.ts
Normal file
33
api/src/model/entitites/log/email.log.entity.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import {
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Entity,
|
||||||
|
ManyToOne,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { KeySystem } from '../system.entity';
|
||||||
|
import { EmailEvent } from 'src/modules/log/log.service';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class EmailLog {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at' })
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@Column({ type: 'text'})
|
||||||
|
to: string;
|
||||||
|
|
||||||
|
@Column({ type: 'text'})
|
||||||
|
message: string;
|
||||||
|
|
||||||
|
@Column({ name: 'type', type: 'text',})
|
||||||
|
type: EmailEvent;
|
||||||
|
|
||||||
|
@ManyToOne(() => KeySystem, { nullable: true })
|
||||||
|
system: KeySystem;
|
||||||
|
|
||||||
|
@Column({type: 'boolean'})
|
||||||
|
success: boolean;
|
||||||
|
}
|
||||||
1
api/src/model/entitites/log/index.ts
Normal file
1
api/src/model/entitites/log/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './email.log.entity';
|
||||||
10
api/src/model/repositories/log/email.log.repository.ts
Normal file
10
api/src/model/repositories/log/email.log.repository.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Repository, DataSource } from 'typeorm';
|
||||||
|
import { EmailLog } from 'src/model/entitites/log';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class EmailLogRepository extends Repository<EmailLog> {
|
||||||
|
constructor(dataSource: DataSource) {
|
||||||
|
super(EmailLog, dataSource.createEntityManager());
|
||||||
|
}
|
||||||
|
}
|
||||||
1
api/src/model/repositories/log/index.ts
Normal file
1
api/src/model/repositories/log/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './email.log.repository';
|
||||||
10
api/src/modules/log/log.module.ts
Normal file
10
api/src/modules/log/log.module.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { LogService } from './log.service';
|
||||||
|
import { DatabaseModule } from 'src/shared/database/database.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [DatabaseModule],
|
||||||
|
providers: [LogService],
|
||||||
|
exports: [LogService]
|
||||||
|
})
|
||||||
|
export class LogModule {}
|
||||||
18
api/src/modules/log/log.service.spec.ts
Normal file
18
api/src/modules/log/log.service.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { LogService } from './log.service';
|
||||||
|
|
||||||
|
describe('LogService', () => {
|
||||||
|
let service: LogService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [LogService],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<LogService>(LogService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
37
api/src/modules/log/log.service.ts
Normal file
37
api/src/modules/log/log.service.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { EmailLogRepository } from 'src/model/repositories/log';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LogService {
|
||||||
|
constructor(private readonly emailLogRepo: EmailLogRepository) {}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
log(type: LogType, data: any) {
|
||||||
|
if (type == LogType.Mail) {
|
||||||
|
return this.logEmail(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async logEmail(data: EmailLogDto) {
|
||||||
|
const log = this.emailLogRepo.create(data);
|
||||||
|
const logEntry = await this.emailLogRepo.save(log);
|
||||||
|
console.log(logEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum LogType {
|
||||||
|
Mail
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum EmailEvent {
|
||||||
|
GrantSystemAccess,
|
||||||
|
RemoveSystemAccess
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EmailLogDto {
|
||||||
|
success: boolean;
|
||||||
|
message: string;
|
||||||
|
to: string;
|
||||||
|
type: EmailEvent;
|
||||||
|
}
|
||||||
45
api/src/modules/mail/mail.module.ts
Normal file
45
api/src/modules/mail/mail.module.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { MailerModule } from '@nestjs-modules/mailer';
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { MailService } from './mail.service';
|
||||||
|
import { LogModule } from '../log/log.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
LogModule,
|
||||||
|
MailerModule.forRootAsync({
|
||||||
|
imports: [ ],
|
||||||
|
inject: [ ConfigService ],
|
||||||
|
useFactory: async (config: ConfigService) => ({
|
||||||
|
transport: {
|
||||||
|
host: config.get('MAILER_HOST'),
|
||||||
|
secure: config.get('MAILER_SECURE'),
|
||||||
|
port: config.get('MAILER_PORT'),
|
||||||
|
auth: {
|
||||||
|
user: config.get('MAILER_USERNAME'),
|
||||||
|
pass: config.get('MAILER_PASSWORD'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaults: {
|
||||||
|
from: config.get('MAILER_FROM'),
|
||||||
|
},
|
||||||
|
template: {
|
||||||
|
dir: join(__dirname, '../../../templates'),
|
||||||
|
adapter: new HandlebarsAdapter(), // or new PugAdapter() or new EjsAdapter()
|
||||||
|
options: {
|
||||||
|
strict: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
],
|
||||||
|
providers: [MailService],
|
||||||
|
exports: [MailService]
|
||||||
|
})
|
||||||
|
export class MailModule {
|
||||||
|
constructor() {
|
||||||
|
console.log(join(__dirname, '../../../templates'))
|
||||||
|
}
|
||||||
|
}
|
||||||
68
api/src/modules/mail/mail.service.ts
Normal file
68
api/src/modules/mail/mail.service.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { MailerService } from "@nestjs-modules/mailer";
|
||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
import { ConfigService } from "@nestjs/config";
|
||||||
|
import { EmailEvent, LogService, LogType } from "../log/log.service";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MailService {
|
||||||
|
constructor(
|
||||||
|
private mailserService: MailerService,
|
||||||
|
private readonly configService: ConfigService,
|
||||||
|
private readonly logService: LogService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendAccessGrantedMail({to, firstName, systemName}: {to: string, firstName: string, systemName: string}) {
|
||||||
|
this.mailserService.sendMail({
|
||||||
|
template: './access',
|
||||||
|
to: to,
|
||||||
|
from: this.configService.get<string>('MAILER_FROM'),
|
||||||
|
subject: 'Zugriff gewährt',
|
||||||
|
context: {
|
||||||
|
firstName,
|
||||||
|
systemName
|
||||||
|
}
|
||||||
|
}).then(v => {
|
||||||
|
this.logService.log(LogType.Mail, {
|
||||||
|
to,
|
||||||
|
success: true,
|
||||||
|
message: v.response,
|
||||||
|
type: EmailEvent.GrantSystemAccess
|
||||||
|
})
|
||||||
|
}).catch(e => {
|
||||||
|
this.logService.log(LogType.Mail, {
|
||||||
|
to,
|
||||||
|
success: false,
|
||||||
|
message: e.response,
|
||||||
|
type: EmailEvent.GrantSystemAccess
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
sendAccessRemovedMail({to, firstName, systemName}: {to: string, firstName: string, systemName: string}) {
|
||||||
|
this.mailserService.sendMail({
|
||||||
|
template: './access-removed',
|
||||||
|
to: to,
|
||||||
|
from: this.configService.get<string>('MAILER_FROM'),
|
||||||
|
subject: 'Zugriff entzogen',
|
||||||
|
context: {
|
||||||
|
firstName,
|
||||||
|
systemName
|
||||||
|
}
|
||||||
|
}).then(v => {
|
||||||
|
this.logService.log(LogType.Mail, {
|
||||||
|
to,
|
||||||
|
success: true,
|
||||||
|
message: v.response,
|
||||||
|
type: EmailEvent.RemoveSystemAccess
|
||||||
|
})
|
||||||
|
}).catch(e => {
|
||||||
|
this.logService.log(LogType.Mail, {
|
||||||
|
to,
|
||||||
|
success: false,
|
||||||
|
message: e.response,
|
||||||
|
type: EmailEvent.RemoveSystemAccess
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,10 +3,11 @@ import { SystemService } from './system.service';
|
|||||||
import { SystemController } from './system.controller';
|
import { SystemController } from './system.controller';
|
||||||
import { AuthModule } from '../auth/auth.module';
|
import { AuthModule } from '../auth/auth.module';
|
||||||
import { DatabaseModule } from 'src/shared/database/database.module';
|
import { DatabaseModule } from 'src/shared/database/database.module';
|
||||||
|
import { MailModule } from '../mail/mail.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [SystemController],
|
controllers: [SystemController],
|
||||||
providers: [SystemService],
|
providers: [SystemService],
|
||||||
imports: [AuthModule, DatabaseModule],
|
imports: [AuthModule, DatabaseModule, MailModule],
|
||||||
})
|
})
|
||||||
export class SystemModule {}
|
export class SystemModule {}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { UpdateSystemDto } from './dto/update-system.dto';
|
|||||||
import { ActivityRepository, KeySystemRepository, UserRepository } from 'src/model/repositories';
|
import { ActivityRepository, KeySystemRepository, UserRepository } from 'src/model/repositories';
|
||||||
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 { MailService } from '../mail/mail.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SystemService {
|
export class SystemService {
|
||||||
@@ -11,6 +12,7 @@ export class SystemService {
|
|||||||
private systemRepo: KeySystemRepository,
|
private systemRepo: KeySystemRepository,
|
||||||
private userRepo: UserRepository,
|
private userRepo: UserRepository,
|
||||||
private systemActivityRepo: ActivityRepository,
|
private systemActivityRepo: ActivityRepository,
|
||||||
|
private mailService: MailService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async create(user: User, createSystemDto: CreateSystemDto) {
|
async create(user: User, createSystemDto: CreateSystemDto) {
|
||||||
@@ -80,6 +82,7 @@ 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})
|
||||||
return sys.managers;
|
return sys.managers;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +97,7 @@ export class SystemService {
|
|||||||
|
|
||||||
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})
|
||||||
return sys.managers;
|
return sys.managers;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
SSOUser,
|
SSOUser,
|
||||||
User,
|
User,
|
||||||
} from 'src/model/entitites';
|
} from 'src/model/entitites';
|
||||||
|
import { EmailLog } from 'src/model/entitites/log';
|
||||||
import { KeySystem } from 'src/model/entitites/system.entity';
|
import { KeySystem } from 'src/model/entitites/system.entity';
|
||||||
import {
|
import {
|
||||||
ActivityRepository,
|
ActivityRepository,
|
||||||
@@ -22,6 +23,7 @@ import {
|
|||||||
UserRepository,
|
UserRepository,
|
||||||
} 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';
|
||||||
|
|
||||||
const ENTITIES = [
|
const ENTITIES = [
|
||||||
User,
|
User,
|
||||||
@@ -33,6 +35,7 @@ const ENTITIES = [
|
|||||||
Customer,
|
Customer,
|
||||||
KeyHandout,
|
KeyHandout,
|
||||||
Activity,
|
Activity,
|
||||||
|
EmailLog,
|
||||||
];
|
];
|
||||||
const REPOSITORIES = [
|
const REPOSITORIES = [
|
||||||
UserRepository,
|
UserRepository,
|
||||||
@@ -44,6 +47,7 @@ const REPOSITORIES = [
|
|||||||
CustomerRepository,
|
CustomerRepository,
|
||||||
KeyHandoutRepository,
|
KeyHandoutRepository,
|
||||||
ActivityRepository,
|
ActivityRepository,
|
||||||
|
EmailLogRepository
|
||||||
];
|
];
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
|||||||
94
api/src/templates/access-removed.hbs
Normal file
94
api/src/templates/access-removed.hbs
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Zugriff entzogen</title>
|
||||||
|
<style>
|
||||||
|
/* General styles */
|
||||||
|
body {
|
||||||
|
font-family: 'Roboto', Arial, sans-serif;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
color: #424242;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 20px auto;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background-color: #2196f3; /* Freundliches Blau */
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.header h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 24px 20px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #424242;
|
||||||
|
}
|
||||||
|
.content p {
|
||||||
|
margin: 0 0 16px;
|
||||||
|
}
|
||||||
|
.button-container {
|
||||||
|
text-align: center;
|
||||||
|
margin: 24px 0;
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 12px 24px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: #2196f3; /* Gleicher Blauton */
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 24px;
|
||||||
|
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.2);
|
||||||
|
transition: background-color 0.3s ease, box-shadow 0.3s ease;
|
||||||
|
}
|
||||||
|
.btn:hover {
|
||||||
|
background-color: #1769aa; /* Dunkleres Blau für Hover */
|
||||||
|
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
text-align: center;
|
||||||
|
padding: 16px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #757575;
|
||||||
|
}
|
||||||
|
.footer a {
|
||||||
|
color: #2196f3;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.footer a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>Von Schließanlage entfernt</h1>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<p>Hallo {{firstName}},</p>
|
||||||
|
<p>Du wurdest als Verwalter folgender Schließanlage entfernt:</p>
|
||||||
|
<div class="button-container">
|
||||||
|
<a href="#" class="btn">{{systemName}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<p>Falls du Fragen hast, kontaktiere uns bitte <a href="mailto:support@example.com">hier</a>.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
94
api/src/templates/access.hbs
Normal file
94
api/src/templates/access.hbs
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Zugriff gewährt</title>
|
||||||
|
<style>
|
||||||
|
/* General styles */
|
||||||
|
body {
|
||||||
|
font-family: 'Roboto', Arial, sans-serif;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
color: #424242;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 20px auto;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background-color: #2196f3; /* Freundliches Blau */
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.header h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 24px 20px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #424242;
|
||||||
|
}
|
||||||
|
.content p {
|
||||||
|
margin: 0 0 16px;
|
||||||
|
}
|
||||||
|
.button-container {
|
||||||
|
text-align: center;
|
||||||
|
margin: 24px 0;
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 12px 24px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: #2196f3; /* Gleicher Blauton */
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 24px;
|
||||||
|
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.2);
|
||||||
|
transition: background-color 0.3s ease, box-shadow 0.3s ease;
|
||||||
|
}
|
||||||
|
.btn:hover {
|
||||||
|
background-color: #1769aa; /* Dunkleres Blau für Hover */
|
||||||
|
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
text-align: center;
|
||||||
|
padding: 16px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #757575;
|
||||||
|
}
|
||||||
|
.footer a {
|
||||||
|
color: #2196f3;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.footer a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>Zu Schließanlage hinzugefügt</h1>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<p>Hallo {{firstName}},</p>
|
||||||
|
<p>Du wurdest als Verwalter zu folgender Schließanlage hinzugefügt:</p>
|
||||||
|
<div class="button-container">
|
||||||
|
<a href="#" class="btn">{{systemName}}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<p>Falls du Fragen hast, kontaktiere uns bitte <a href="mailto:support@example.com">hier</a>.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user