9 Commits
1.1.1 ... 1.2.2

Author SHA1 Message Date
2f25937a94 feat: add ical 2025-03-18 23:57:01 +01:00
658b8d4dd8 chore: 1.2.1 2025-03-18 23:38:08 +01:00
df6381fd5e feat: new email templates 2025-03-18 23:37:28 +01:00
a11e5f2f3e ci: drop main branch
All checks were successful
ci/woodpecker/push/build Pipeline was successful
2024-12-18 18:01:12 +01:00
7a8e484632 chore: 1.2.0
Some checks failed
ci/woodpecker/push/build Pipeline failed
ci/woodpecker/tag/release Pipeline was successful
2024-12-18 18:00:06 +01:00
1f771fb73f feat: NODE_ENV=production 2024-12-18 17:59:40 +01:00
34c8f03571 refactor: dockerfile 2024-12-18 17:59:30 +01:00
dfac4c0fe9 chore(deps): bun:1.1.40 2024-12-18 17:59:24 +01:00
9860014420 chore(deps): bump all
Some checks failed
ci/woodpecker/push/build Pipeline failed
2024-12-18 17:59:10 +01:00
10 changed files with 225 additions and 66 deletions

View File

@@ -10,3 +10,4 @@ DOCUMENT_SERVER_URL="https://documents.run.lauf-fuer-kaya.de"
AUTHKEY="" AUTHKEY=""
EVENT_DATE="23.05.2025" EVENT_DATE="23.05.2025"
EVENT_NAME="Lauf für Kaya! 2025" EVENT_NAME="Lauf für Kaya! 2025"
NODE_ENV=production

View File

@@ -1,19 +1,4 @@
steps: steps:
- name: build latest
image: woodpeckerci/plugin-docker-buildx
settings:
repo: registry.odit.services/lfk/mailer
tags:
- latest
registry: registry.odit.services
platforms: linux/amd64,linux/arm64
cache_from: registry.odit.services/lfk/mailer:dev
username:
from_secret: odit-registry-builder-username
password:
from_secret: odit-registry-builder-password
when:
branch: main
- name: build dev - name: build dev
image: woodpeckerci/plugin-docker-buildx image: woodpeckerci/plugin-docker-buildx
settings: settings:

View File

@@ -1,21 +1,7 @@
FROM oven/bun:1.1.36-slim FROM oven/bun:1.1.40-slim
# FROM oven/bun:1.0.25
WORKDIR /app WORKDIR /app
# Copy package files
# Install dependencies
COPY package.json . COPY package.json .
RUN bun i RUN bun i
# COPY package.json bun.lockb ./
# RUN bun install --frozen-lockfile
# Copy source code
COPY . . COPY . .
# Expose the application port
EXPOSE 3000 EXPOSE 3000
# Start the application
CMD ["bun", "run", "start"] CMD ["bun", "run", "start"]

BIN
bun.lockb

Binary file not shown.

View File

@@ -1,6 +1,6 @@
{ {
"name": "@odit/lfk-mailer", "name": "@odit/lfk-mailer",
"version": "1.1.1", "version": "1.2.1",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -11,10 +11,10 @@
"@hono/node-server": "1.13.7", "@hono/node-server": "1.13.7",
"@hono/swagger-ui": "0.5.0", "@hono/swagger-ui": "0.5.0",
"@hono/zod-openapi": "0.18.3", "@hono/zod-openapi": "0.18.3",
"@hono/zod-validator": "0.4.1", "@hono/zod-validator": "0.4.2",
"bullmq": "5.34.0", "bullmq": "5.34.3",
"handlebars": "4.7.8", "handlebars": "4.7.8",
"hono": "4.6.13", "hono": "4.6.14",
"ioredis": "5.4.1", "ioredis": "5.4.1",
"nodemailer": "6.9.16", "nodemailer": "6.9.16",
"zod": "3.24.1" "zod": "3.24.1"
@@ -22,6 +22,6 @@
"devDependencies": { "devDependencies": {
"@types/node": "22.10.2", "@types/node": "22.10.2",
"@types/nodemailer": "6.4.17", "@types/nodemailer": "6.4.17",
"bun-types": "1.1.38" "bun-types": "1.1.40"
} }
} }

View File

@@ -2,11 +2,13 @@ import { createTransport } from "nodemailer";
import { Queue, Worker, QueueEvents } from "bullmq"; import { Queue, Worker, QueueEvents } from "bullmq";
import { config } from "../config/env"; import { config } from "../config/env";
import Redis from "ioredis"; import Redis from "ioredis";
import { Attachment } from "nodemailer/lib/mailer";
interface EmailJob { interface EmailJob {
to: string; to: string;
subject: string; subject: string;
html: string; html: string;
attachments: Attachment[];
text: string; text: string;
} }
@@ -40,12 +42,13 @@ const worker = new Worker<EmailJob>(
QUEUE_NAME, QUEUE_NAME,
async (job) => { async (job) => {
await transporter.sendMail({ await transporter.sendMail({
from: config.email.from, from: { address: config.email.from, name: "Lauf für Kaya!" },
replyTo: config.email.replyTo, replyTo: config.email.replyTo,
to: job.data.to, to: job.data.to,
subject: job.data.subject, subject: job.data.subject,
text: job.data.text, text: job.data.text,
html: job.data.html, html: job.data.html,
attachments: job.data.attachments,
}); });
}, },
{ {

View File

@@ -5,6 +5,7 @@ import { z } from 'zod'
import { EmailService } from '../services/email' import { EmailService } from '../services/email'
import { getEmailTemplate } from '../templates' import { getEmailTemplate } from '../templates'
import { Language } from '../types' import { Language } from '../types'
import { Attachment } from 'nodemailer/lib/mailer'
const emailRouter = new Hono() const emailRouter = new Hono()
const emailService = new EmailService() const emailService = new EmailService()
@@ -22,7 +23,7 @@ async function generateBarcodeDataURL(data) {
emailRouter.post('/', bearerAuth({ token: process.env.AUTHKEY }), zValidator('json', sendEmailSchema), async (c) => { emailRouter.post('/', bearerAuth({ token: process.env.AUTHKEY }), zValidator('json', sendEmailSchema), async (c) => {
let { to, templateName, language, data } = c.req.valid('json') let { to, templateName, language, data } = c.req.valid('json')
const attachments: Attachment[] = []
try { try {
const template = getEmailTemplate(templateName, language as Language) const template = getEmailTemplate(templateName, language as Language)
if (templateName === "welcome") { if (templateName === "welcome") {
@@ -32,6 +33,12 @@ emailRouter.post('/', bearerAuth({ token: process.env.AUTHKEY }), zValidator('js
} else { } else {
return c.json({ success: false, error: "required params 'data.name', 'data.link', 'data.barcode_content' not provided" }, 406) return c.json({ success: false, error: "required params 'data.name', 'data.link', 'data.barcode_content' not provided" }, 406)
} }
const attachment: Attachment = {
filename: 'invite.ics',
content: `BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//ICS Generator//NONSGML ICS Generator//DE\nBEGIN:VEVENT\nUID:1742337822408-5ghrzyi@icsgenerator.local\nDTSTAMP:20250318T224342Z\nSUMMARY:Lauf für Kaya! 2025\nDTSTART:20250523T110000Z\nDTEND:20250523T160000Z\nDESCRIPTION:Der Lauf für Kaya! 2025 findet am 23.05.2025 auf dem Sportplatz des Gymnasium Herzogenaurach statt. Zur Anmeldung einfach zum Infozelt kommen.\nLOCATION:Sportplatz Gymnasium Herzogenaurach\nBEGIN:VALARM\nACTION:DISPLAY\nDESCRIPTION:Erinnerung: Lauf für Kaya! 2025\nTRIGGER:-PT1440M\nEND:VALARM\nEND:VEVENT\nEND:VCALENDAR`,
contentType: 'text/calendar; method=REQUEST',
}
attachments.push(attachment)
} }
if (templateName === "password-reset") { if (templateName === "password-reset") {
if (data.token) { if (data.token) {
@@ -45,6 +52,7 @@ emailRouter.post('/', bearerAuth({ token: process.env.AUTHKEY }), zValidator('js
data.event_name = process.env.EVENT_NAME data.event_name = process.env.EVENT_NAME
await emailService.sendEmail({ await emailService.sendEmail({
to, to,
attachments,
subject: template.subject(data), subject: template.subject(data),
html: template.html(data), html: template.html(data),
text: template.text(data) text: template.text(data)

View File

@@ -1,10 +1,12 @@
import { emailQueue } from '../queues/email.queue' import { emailQueue } from '../queues/email.queue'
import { config } from '../config/env' import { config } from '../config/env'
import { Attachment } from 'nodemailer/lib/mailer'
interface EmailOptions { interface EmailOptions {
to: string to: string
subject: string subject: string
html: string html: string
attachments: Attachment[]
text: string text: string
} }

View File

@@ -175,8 +175,10 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
" "
> >
Am Lauftag (<strong>{{event_date}}</strong>) musst du nur noch diesen Am Lauftag (<strong>{{event_date}}</strong>) musst du nur noch
Barcode vorzeigen, damit erhältst du deine Läuferkarte. diesen Barcode vorzeigen, damit erhältst du deine Läuferkarte.<br />Der
Bürger- & Firmenlauf findet von
<strong>13:00 bis 18:00 Uhr</strong> statt.
</p> </p>
<table <table
align="center" align="center"
@@ -223,20 +225,103 @@
" "
> >
Deinen Registrierungs-Code, Rundenzeiten und weitere Infos kannst Deinen Registrierungs-Code, Rundenzeiten und weitere Infos kannst
du jederzeit im du jederzeit im Lauf für Kaya! Selfservice einsehen.
</p>
<table
align="center"
width="100%"
border="0"
cellpadding="0"
cellspacing="0"
role="presentation"
style="max-width: 100%; text-align: center; margin-bottom: 20px"
>
<tbody>
<tr style="width: 100%">
<td>
<a <a
href="{{link}}" href="{{link}}"
rel="noopener noreferrer nofollow"
style=" style="
color: #111827; line-height: 100%;
text-decoration: underline; text-decoration: none;
display: inline-block;
max-width: 100%;
mso-padding-alt: 0px;
color: #ffffff;
background-color: #3a4bdd;
border-color: #3a4bdd;
border-width: 2px;
border-style: solid;
font-size: 14px;
font-weight: 500; font-weight: 500;
border-radius: 0px;
padding: 12px 32px 12px 32px;
" "
target="_blank" target="_blank"
>Lauf für Kaya! Selfservice</a ><span
><!--[if mso
]><i
style="mso-font-width: 400%; mso-text-raise: 18"
hidden
>&#8202;&#8202;&#8202;&#8202;</i
><!
[endif]--></span
><span
style="
max-width: 100%;
display: inline-block;
line-height: 120%;
mso-padding-alt: 0px;
mso-text-raise: 9px;
"
>Zum Selfservice Portal</span
><span
><!--[if mso
]><i style="mso-font-width: 400%" hidden
>&#8202;&#8202;&#8202;&#8202;&#8203;</i
><!
[endif]--></span
></a
>
</td>
</tr>
</tbody>
</table>
<p
style="
font-size: 15px;
line-height: 26.25px;
margin: 0 0 20px 0;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #374151;
"
>
Wenn der Button nicht funktioniert, kannst du diesen Link in
deinen Browser kopieren:
<code
style="
background-color: #efefef;
color: #111827;
padding: 2px 4px;
border-radius: 6px;
font-family: SFMono-Regular, Menlo, Monaco, Consolas,
'Liberation Mono', 'Courier New', monospace;
font-weight: 400;
letter-spacing: 0;
"
>{{link}}</code
> >
einsehen.
</p> </p>
<hr
style="
width: 100%;
border: none;
border-top: 1px solid #eaeaea;
margin-top: 32px;
margin-bottom: 32px;
"
/>
<p <p
style=" style="
font-size: 15px; font-size: 15px;

View File

@@ -175,7 +175,10 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
" "
> >
On the day of the run (<strong>{{event_date}}</strong>) you only have to show your barcode to receive your runner's card. On the day of the run (<strong>{{event_date}}</strong>) you only
have to show your barcode to receive your runner's card.<br />The
Citizens' & Company Run will take place from
<strong> 1:00 p.m. to 6:00 p.m.</strong>
</p> </p>
<table <table
align="center" align="center"
@@ -221,19 +224,104 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
" "
> >
You can view your registration code, lap times and further information at any time from the You can view your registration code, lap times and further
information at any time from the Lauf für Kaya! Selfservice
</p>
<table
align="center"
width="100%"
border="0"
cellpadding="0"
cellspacing="0"
role="presentation"
style="max-width: 100%; text-align: center; margin-bottom: 20px"
>
<tbody>
<tr style="width: 100%">
<td>
<a <a
href="{{link}}" href="{{link}}"
rel="noopener noreferrer nofollow"
style=" style="
color: #111827; line-height: 100%;
text-decoration: underline; text-decoration: none;
display: inline-block;
max-width: 100%;
mso-padding-alt: 0px;
color: #ffffff;
background-color: #3a4bdd;
border-color: #3a4bdd;
border-width: 2px;
border-style: solid;
font-size: 14px;
font-weight: 500; font-weight: 500;
border-radius: 0px;
padding: 12px 32px 12px 32px;
" "
target="_blank" target="_blank"
>Lauf für Kaya! Selfservice</a ><span
><!--[if mso
]><i
style="mso-font-width: 400%; mso-text-raise: 18"
hidden
>&#8202;&#8202;&#8202;&#8202;</i
><!
[endif]--></span
><span
style="
max-width: 100%;
display: inline-block;
line-height: 120%;
mso-padding-alt: 0px;
mso-text-raise: 9px;
"
>To the self-service portal</span
><span
><!--[if mso
]><i style="mso-font-width: 400%" hidden
>&#8202;&#8202;&#8202;&#8202;&#8203;</i
><!
[endif]--></span
></a
>
</td>
</tr>
</tbody>
</table>
<p
style="
font-size: 15px;
line-height: 26.25px;
margin: 0 0 20px 0;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #374151;
"
>
If the button doesn't work, you can copy this link into your
browser:
<code
style="
background-color: #efefef;
color: #111827;
padding: 2px 4px;
border-radius: 6px;
font-family: SFMono-Regular, Menlo, Monaco, Consolas,
'Liberation Mono', 'Courier New', monospace;
font-weight: 400;
letter-spacing: 0;
"
>{{link}}</code
> >
</p> </p>
<hr
style="
width: 100%;
border: none;
border-top: 1px solid #eaeaea;
margin-top: 32px;
margin-bottom: 32px;
"
/>
<p <p
style=" style="
font-size: 15px; font-size: 15px;
@@ -262,7 +350,8 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
" "
> >
If you have any questions, please reply to this e-mail at any time or write to If you have any questions, please reply to this e-mail at any time
or write to
<u <u
><strong ><strong
><a ><a