feat: improve config UI (#69)

* add first concept

* completed configuration ui update

* add button for testing email configuration

* improve mobile layout

* add migration

* run formatter

* delete unnecessary modal

* remove unused comment
This commit is contained in:
Elias Schneider
2022-12-30 14:40:23 +01:00
committed by GitHub
parent e5b50f855c
commit 5bc4f902f6
23 changed files with 429 additions and 284 deletions

View File

@@ -1,18 +1,20 @@
import {
BadRequestException,
ForbiddenException,
Injectable,
UnauthorizedException,
} from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { User } from "@prisma/client";
import * as argon from "argon2";
import * as crypto from "crypto";
import { authenticator, totp } from "otplib";
import * as qrcode from "qrcode-svg";
import { ConfigService } from "src/config/config.service";
import { PrismaService } from "src/prisma/prisma.service";
import { AuthService } from "./auth.service";
import { AuthSignInTotpDTO } from "./dto/authSignInTotp.dto";
@Injectable()
export class AuthTotpService {
constructor(
private config: ConfigService,

View File

@@ -1,22 +1,19 @@
import {
Body,
Controller,
Get,
Param,
Patch,
Post,
UseGuards,
} from "@nestjs/common";
import { Body, Controller, Get, Patch, Post, UseGuards } from "@nestjs/common";
import { AdministratorGuard } from "src/auth/guard/isAdmin.guard";
import { JwtGuard } from "src/auth/guard/jwt.guard";
import { EmailService } from "src/email/email.service";
import { ConfigService } from "./config.service";
import { AdminConfigDTO } from "./dto/adminConfig.dto";
import { ConfigDTO } from "./dto/config.dto";
import { TestEmailDTO } from "./dto/testEmail.dto";
import UpdateConfigDTO from "./dto/updateConfig.dto";
@Controller("configs")
export class ConfigController {
constructor(private configService: ConfigService) {}
constructor(
private configService: ConfigService,
private emailService: EmailService
) {}
@Get()
async list() {
@@ -31,12 +28,10 @@ export class ConfigController {
);
}
@Patch("admin/:key")
@Patch("admin")
@UseGuards(JwtGuard, AdministratorGuard)
async update(@Param("key") key: string, @Body() data: UpdateConfigDTO) {
return new AdminConfigDTO().from(
await this.configService.update(key, data.value)
);
async updateMany(@Body() data: UpdateConfigDTO[]) {
await this.configService.updateMany(data);
}
@Post("admin/finishSetup")
@@ -44,4 +39,10 @@ export class ConfigController {
async finishSetup() {
return await this.configService.finishSetup();
}
@Post("admin/testEmail")
@UseGuards(JwtGuard, AdministratorGuard)
async testEmail(@Body() { email }: TestEmailDTO) {
await this.emailService.sendTestMail(email);
}
}

View File

@@ -1,10 +1,12 @@
import { Global, Module } from "@nestjs/common";
import { EmailModule } from "src/email/email.module";
import { PrismaService } from "src/prisma/prisma.service";
import { ConfigController } from "./config.controller";
import { ConfigService } from "./config.service";
@Global()
@Module({
imports: [EmailModule],
providers: [
{
provide: "CONFIG_VARIABLES",

View File

@@ -39,6 +39,14 @@ export class ConfigService {
});
}
async updateMany(data: { key: string; value: string | number | boolean }[]) {
for (const variable of data) {
await this.update(variable.key, variable.value);
}
return data;
}
async update(key: string, value: string | number | boolean) {
const configVariable = await this.prisma.config.findUnique({
where: { key },

View File

@@ -14,6 +14,9 @@ export class AdminConfigDTO extends ConfigDTO {
@Expose()
obscured: boolean;
@Expose()
category: string;
from(partial: Partial<AdminConfigDTO>) {
return plainToClass(AdminConfigDTO, partial, {
excludeExtraneousValues: true,

View File

@@ -0,0 +1,7 @@
import { IsEmail, IsNotEmpty } from "class-validator";
export class TestEmailDTO {
@IsEmail()
@IsNotEmpty()
email: string;
}

View File

@@ -1,6 +1,9 @@
import { IsNotEmpty, ValidateIf } from "class-validator";
import { IsNotEmpty, IsString, ValidateIf } from "class-validator";
class UpdateConfigDTO {
@IsString()
key: string;
@IsNotEmpty()
@ValidateIf((dto) => dto.value !== "")
value: string | number | boolean;

View File

@@ -7,24 +7,23 @@ import { ConfigService } from "src/config/config.service";
export class EmailService {
constructor(private config: ConfigService) {}
async sendMail(recipientEmail: string, shareId: string, creator: User) {
// create reusable transporter object using the default SMTP transport
const transporter = nodemailer.createTransport({
host: this.config.get("SMTP_HOST"),
port: parseInt(this.config.get("SMTP_PORT")),
secure: parseInt(this.config.get("SMTP_PORT")) == 465,
auth: {
user: this.config.get("SMTP_USERNAME"),
pass: this.config.get("SMTP_PASSWORD"),
},
});
transporter = nodemailer.createTransport({
host: this.config.get("SMTP_HOST"),
port: parseInt(this.config.get("SMTP_PORT")),
secure: parseInt(this.config.get("SMTP_PORT")) == 465,
auth: {
user: this.config.get("SMTP_USERNAME"),
pass: this.config.get("SMTP_PASSWORD"),
},
});
async sendMail(recipientEmail: string, shareId: string, creator: User) {
if (!this.config.get("ENABLE_EMAIL_RECIPIENTS"))
throw new InternalServerErrorException("Email service disabled");
const shareUrl = `${this.config.get("APP_URL")}/share/${shareId}`;
await transporter.sendMail({
await this.transporter.sendMail({
from: `"Pingvin Share" <${this.config.get("SMTP_EMAIL")}>`,
to: recipientEmail,
subject: this.config.get("EMAIL_SUBJECT"),
@@ -35,4 +34,13 @@ export class EmailService {
.replaceAll("{shareUrl}", shareUrl),
});
}
async sendTestMail(recipientEmail: string) {
await this.transporter.sendMail({
from: `"Pingvin Share" <${this.config.get("SMTP_EMAIL")}>`,
to: recipientEmail,
subject: "Test email",
text: "This is a test email",
});
}
}