Merge pull request 'Email verification Backend' (#72) from Backend_Fix into main
Reviewed-on: #72
This commit was merged in pull request #72.
This commit is contained in:
Generated
+582
-41
File diff suppressed because it is too large
Load Diff
@@ -50,25 +50,25 @@
|
||||
"nodemailer": "^7.0.5",
|
||||
"pg": "^8.16.3",
|
||||
"redis": "^5.8.1",
|
||||
"sharp": "^0.34.4",
|
||||
"socket.io": "^4.8.1",
|
||||
"swagger-jsdoc": "^6.2.8",
|
||||
"swagger-ui-express": "^5.0.1",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"typeorm": "^0.3.26",
|
||||
"uuid": "^11.1.0",
|
||||
"winston": "^3.17.0",
|
||||
"swagger-jsdoc": "^6.2.8",
|
||||
"swagger-ui-express": "^5.0.1"
|
||||
"winston": "^3.17.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/multer": "^2.0.0",
|
||||
"@types/nodemailer": "^7.0.1",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@jest/globals": "^30.0.5",
|
||||
"@types/bcrypt": "^6.0.0",
|
||||
"@types/cookie-parser": "^1.4.9",
|
||||
"@types/express": "^5.0.3",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/jsonwebtoken": "^9.0.10",
|
||||
"@types/multer": "^2.0.0",
|
||||
"@types/node": "^24.3.3",
|
||||
"@types/nodemailer": "^7.0.1",
|
||||
"@types/pg": "^8.15.5",
|
||||
"@types/redis": "^4.0.10",
|
||||
"@types/socket.io": "^3.0.1",
|
||||
@@ -76,6 +76,7 @@
|
||||
"@types/supertest": "^6.0.3",
|
||||
"@types/swagger-jsdoc": "^6.0.4",
|
||||
"@types/swagger-ui-express": "^4.1.8",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"jest": "^30.0.5",
|
||||
"nodemon": "^3.1.10",
|
||||
"rimraf": "^5.0.10",
|
||||
|
||||
@@ -385,11 +385,12 @@ router.patch('/decks/:id', adminRequired, async (req: Request, res: Response) =>
|
||||
// Hard delete deck (admin only)
|
||||
router.delete('/decks/:id/hard', adminRequired, async (req: Request, res: Response) => {
|
||||
try {
|
||||
const adminUserId = (req as any).user.userId;
|
||||
const deckId = req.params.id;
|
||||
logRequest('Admin hard delete deck endpoint accessed', req, res, { deckId });
|
||||
|
||||
const result = await container.deleteDeckCommandHandler.execute({ id: deckId, soft: false });
|
||||
|
||||
|
||||
const result = await container.deleteDeckCommandHandler.execute({ userid: adminUserId, authLevel: 1, id: deckId, soft: false });
|
||||
|
||||
logRequest('Admin deck hard delete successful', req, res, { deckId, success: result });
|
||||
res.json({ success: result });
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as nodemailer from 'nodemailer';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import sharp from 'sharp';
|
||||
import { logError, logAuth, logStartup } from './Logger';
|
||||
import { EmailTemplateHelper, LocalizedSubjects } from './EmailTemplateHelper';
|
||||
|
||||
@@ -28,9 +29,14 @@ export class EmailService {
|
||||
private transporter!: nodemailer.Transporter;
|
||||
private config: EmailConfig;
|
||||
private templatesPath: string;
|
||||
private logoPath: string;
|
||||
private resizedLogoBuffer?: Buffer;
|
||||
|
||||
constructor() {
|
||||
this.templatesPath = path.join(__dirname, '../../Templates');
|
||||
this.logoPath = path.join(__dirname, '../../../assets/Logo.png');
|
||||
// Load logo asynchronously after initialization
|
||||
this.loadLogoBase64().catch(err => console.error('[EmailService] Error loading logo:', err));
|
||||
|
||||
this.config = {
|
||||
host: process.env.EMAIL_HOST || 'smtp.gmail.com',
|
||||
@@ -63,6 +69,32 @@ export class EmailService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and resize logo for email attachments - 32x32 pixels
|
||||
*/
|
||||
private async loadLogoBase64(): Promise<void> {
|
||||
try {
|
||||
if (fs.existsSync(this.logoPath)) {
|
||||
const logoBuffer = fs.readFileSync(this.logoPath);
|
||||
|
||||
// Resize to 60x60 pixels with high quality and centered
|
||||
this.resizedLogoBuffer = await sharp(logoBuffer)
|
||||
.resize(60, 60, {
|
||||
fit: 'contain',
|
||||
background: { r: 255, g: 255, b: 255, alpha: 1 },
|
||||
position: 'center'
|
||||
})
|
||||
.png()
|
||||
.toBuffer();
|
||||
|
||||
console.log(`[EmailService] ✅ Logo loaded and resized to 60x60`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[EmailService] ❌ Failed to load/resize logo:`, error);
|
||||
logError('Failed to load logo for emails', error instanceof Error ? error : new Error(String(error)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send email with template
|
||||
* @param options - Email options including template and data
|
||||
@@ -73,19 +105,29 @@ export class EmailService {
|
||||
let textContent = options.text;
|
||||
|
||||
if (options.template) {
|
||||
const templateResult = await this.loadTemplate(options.template, options.templateData || {});
|
||||
const templateResult = await this.loadTemplate(options.template, options.templateData);
|
||||
htmlContent = templateResult.html;
|
||||
textContent = templateResult.text;
|
||||
}
|
||||
|
||||
const mailOptions = {
|
||||
const mailOptions: any = {
|
||||
from: this.config.from,
|
||||
to: options.to,
|
||||
subject: options.subject,
|
||||
html: htmlContent,
|
||||
text: textContent
|
||||
text: textContent,
|
||||
attachments: []
|
||||
};
|
||||
|
||||
// Add logo as CID attachment if available
|
||||
if (this.resizedLogoBuffer) {
|
||||
mailOptions.attachments.push({
|
||||
filename: 'logo.png',
|
||||
content: this.resizedLogoBuffer,
|
||||
cid: 'logo@serpentrace' // Content-ID for referencing in HTML
|
||||
});
|
||||
}
|
||||
|
||||
const result = await this.transporter.sendMail(mailOptions);
|
||||
logAuth('Email sent successfully', undefined, {
|
||||
messageId: result.messageId,
|
||||
|
||||
@@ -22,18 +22,31 @@
|
||||
}
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
.header table {
|
||||
margin: 0 auto;
|
||||
}
|
||||
.header img {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
display: block;
|
||||
}
|
||||
.logo {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #2c5aa0;
|
||||
margin-bottom: 10px;
|
||||
color: #2E7D32;
|
||||
vertical-align: middle;
|
||||
padding-left: 10px;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 18px;
|
||||
color: #666;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid #4CAF50;
|
||||
}
|
||||
.greeting {
|
||||
font-size: 16px;
|
||||
@@ -98,9 +111,14 @@
|
||||
<body>
|
||||
<div class="email-container">
|
||||
<div class="header">
|
||||
<div class="logo">🐍 {{companyName}}</div>
|
||||
<div class="subtitle">Antwort auf Ihre {{contactTypeString}}</div>
|
||||
<table cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td><img src="cid:logo@serpentrace" alt="Logo"/></td>
|
||||
<td class="logo">🐍 SerpentRace</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="subtitle">Antwort auf Ihre {{contactTypeString}}</div>
|
||||
|
||||
<div class="greeting">
|
||||
Hallo {{contactName}},
|
||||
|
||||
@@ -22,18 +22,31 @@
|
||||
}
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
.header table {
|
||||
margin: 0 auto;
|
||||
}
|
||||
.header img {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
display: block;
|
||||
}
|
||||
.logo {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #2c5aa0;
|
||||
margin-bottom: 10px;
|
||||
color: #2E7D32;
|
||||
vertical-align: middle;
|
||||
padding-left: 10px;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 18px;
|
||||
color: #666;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid #4CAF50;
|
||||
}
|
||||
.greeting {
|
||||
font-size: 16px;
|
||||
@@ -98,9 +111,14 @@
|
||||
<body>
|
||||
<div class="email-container">
|
||||
<div class="header">
|
||||
<div class="logo">🐍 {{companyName}}</div>
|
||||
<div class="subtitle">Válasz az Ön {{contactTypeString}} üzenetére</div>
|
||||
<table cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td><img src="cid:logo@serpentrace" alt="Logo"/></td>
|
||||
<td class="logo">🐍 SerpentRace</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="subtitle">Válasz az ön {{contactTypeString}}</div>
|
||||
|
||||
<div class="greeting">
|
||||
Kedves {{contactName}}!
|
||||
|
||||
@@ -22,18 +22,31 @@
|
||||
}
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
.header table {
|
||||
margin: 0 auto;
|
||||
}
|
||||
.header img {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
display: block;
|
||||
}
|
||||
.logo {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #2c5aa0;
|
||||
margin-bottom: 10px;
|
||||
color: #2E7D32;
|
||||
vertical-align: middle;
|
||||
padding-left: 10px;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 18px;
|
||||
color: #666;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid #4CAF50;
|
||||
}
|
||||
.greeting {
|
||||
font-size: 16px;
|
||||
@@ -98,9 +111,14 @@
|
||||
<body>
|
||||
<div class="email-container">
|
||||
<div class="header">
|
||||
<div class="logo">🐍 {{companyName}}</div>
|
||||
<div class="subtitle">Response to Your {{contactTypeString}}</div>
|
||||
<table cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td><img src="cid:logo@serpentrace" alt="Logo"/></td>
|
||||
<td class="logo">🐍 SerpentRace</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="subtitle">Response to Your {{contactTypeString}}</div>
|
||||
|
||||
<div class="greeting">
|
||||
Hello {{contactName}},
|
||||
|
||||
@@ -22,19 +22,31 @@
|
||||
}
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
border-bottom: 2px solid #FF9800;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
.header table {
|
||||
margin: 0 auto;
|
||||
}
|
||||
.header img {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
display: block;
|
||||
}
|
||||
.logo {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #E65100;
|
||||
margin-bottom: 10px;
|
||||
color: #2E7D32;
|
||||
vertical-align: middle;
|
||||
padding-left: 10px;
|
||||
}
|
||||
.subtitle {
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid #4CAF50;
|
||||
}
|
||||
.content {
|
||||
margin-bottom: 30px;
|
||||
@@ -123,9 +135,14 @@
|
||||
<body>
|
||||
<div class="email-container">
|
||||
<div class="header">
|
||||
<div class="logo"><img src="../../assets/Logo.png" alt="{{ companyName }}" /> {{ companyName }}</div>
|
||||
<div class="subtitle">Passwort zurücksetzen</div>
|
||||
<table cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td><img src="cid:logo@serpentrace" alt="Logo"/></td>
|
||||
<td class="logo">SerpentRace</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="subtitle">Passwort zurücksetzen</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="greeting">
|
||||
|
||||
@@ -22,19 +22,31 @@
|
||||
}
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
border-bottom: 2px solid #FF9800;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
.header table {
|
||||
margin: 0 auto;
|
||||
}
|
||||
.header img {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
display: block;
|
||||
}
|
||||
.logo {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #E65100;
|
||||
margin-bottom: 10px;
|
||||
color: #2E7D32;
|
||||
vertical-align: middle;
|
||||
padding-left: 10px;
|
||||
}
|
||||
.subtitle {
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid #4CAF50;
|
||||
}
|
||||
.content {
|
||||
margin-bottom: 30px;
|
||||
@@ -123,9 +135,14 @@
|
||||
<body>
|
||||
<div class="email-container">
|
||||
<div class="header">
|
||||
<div class="logo"><img src="../../assets/Logo.png" alt="{{ companyName }}" /> {{ companyName }}</div>
|
||||
<div class="subtitle">Jelszó visszaállítás kérése</div>
|
||||
<table cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td><img src="cid:logo@serpentrace" alt="Logo"/></td>
|
||||
<td class="logo">SerpentRace</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="subtitle">Jelszó visszaállítás kérése</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="greeting">
|
||||
|
||||
@@ -22,19 +22,31 @@
|
||||
}
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
border-bottom: 2px solid #FF9800;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
.header table {
|
||||
margin: 0 auto;
|
||||
}
|
||||
.header img {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
display: block;
|
||||
}
|
||||
.logo {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #E65100;
|
||||
margin-bottom: 10px;
|
||||
color: #2E7D32;
|
||||
vertical-align: middle;
|
||||
padding-left: 10px;
|
||||
}
|
||||
.subtitle {
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid #4CAF50;
|
||||
}
|
||||
.content {
|
||||
margin-bottom: 30px;
|
||||
@@ -123,9 +135,14 @@
|
||||
<body>
|
||||
<div class="email-container">
|
||||
<div class="header">
|
||||
<div class="logo"><img src="../../assets/Logo.png" alt="{{ companyName }}" /> {{ companyName }}</div>
|
||||
<div class="subtitle">Password Reset Request</div>
|
||||
<table cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td><img src="cid:logo@serpentrace" alt="Logo"/></td>
|
||||
<td class="logo">SerpentRace</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="subtitle">Password Reset Request</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="greeting">
|
||||
|
||||
@@ -22,19 +22,31 @@
|
||||
}
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
border-bottom: 2px solid #4CAF50;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
.header table {
|
||||
margin: 0 auto;
|
||||
}
|
||||
.header img {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
display: block;
|
||||
}
|
||||
.logo {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #2E7D32;
|
||||
margin-bottom: 10px;
|
||||
vertical-align: middle;
|
||||
padding-left: 10px;
|
||||
}
|
||||
.subtitle {
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid #4CAF50;
|
||||
}
|
||||
.content {
|
||||
margin-bottom: 30px;
|
||||
@@ -115,9 +127,14 @@
|
||||
<body>
|
||||
<div class="email-container">
|
||||
<div class="header">
|
||||
<div class="logo"><img src="../../assets/Logo.png" alt="{{ companyName }}" /> {{ companyName }}</div>
|
||||
<div class="subtitle">Konto verifizieren</div>
|
||||
<table cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td><img src="cid:logo@serpentrace" alt="Logo"/></td>
|
||||
<td class="logo">SerpentRace</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="subtitle">Konto verifizieren</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="greeting">
|
||||
|
||||
@@ -22,19 +22,31 @@
|
||||
}
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
border-bottom: 2px solid #4CAF50;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
.header table {
|
||||
margin: 0 auto;
|
||||
}
|
||||
.header img {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
display: block;
|
||||
}
|
||||
.logo {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #2E7D32;
|
||||
margin-bottom: 10px;
|
||||
vertical-align: middle;
|
||||
padding-left: 10px;
|
||||
}
|
||||
.subtitle {
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid #4CAF50;
|
||||
}
|
||||
.content {
|
||||
margin-bottom: 30px;
|
||||
@@ -115,9 +127,14 @@
|
||||
<body>
|
||||
<div class="email-container">
|
||||
<div class="header">
|
||||
<div class="logo"><img src="../../assets/Logo.png" alt="{{ companyName }}" /> {{ companyName }}</div>
|
||||
<div class="subtitle">Fiók megerősítése</div>
|
||||
<table cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td><img src="cid:logo@serpentrace" alt="Logo"/></td>
|
||||
<td class="logo">SerpentRace</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="subtitle">Fiók megerősítése</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="greeting">
|
||||
|
||||
@@ -22,19 +22,32 @@
|
||||
}
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
border-bottom: 2px solid #4CAF50;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
.header table {
|
||||
margin: 0 auto;
|
||||
}
|
||||
.header img {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
display: block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.logo {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #2E7D32;
|
||||
margin-bottom: 10px;
|
||||
vertical-align: middle;
|
||||
padding-left: 10px;
|
||||
}
|
||||
.subtitle {
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 2px solid #4CAF50;
|
||||
}
|
||||
.content {
|
||||
margin-bottom: 30px;
|
||||
@@ -115,9 +128,14 @@
|
||||
<body>
|
||||
<div class="email-container">
|
||||
<div class="header">
|
||||
<div class="logo"><img src="../../assets/Logo.png" alt="{{ companyName }}" /> {{ companyName }}</div>
|
||||
<div class="subtitle">Account Verification</div>
|
||||
<table cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td><img src="cid:logo@serpentrace" alt="Logo"/></td>
|
||||
<td class="logo">SerpentRace</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="subtitle">Account Verification</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="greeting">
|
||||
|
||||
Reference in New Issue
Block a user