Compare commits

...

19 Commits

Author SHA1 Message Date
8427d348dd fix: ensure smtp metadata logger doesn't crash in tests
Some checks failed
🚀 Build & Deploy / 🔍 Prepare (push) Successful in 4s
🚀 Build & Deploy / 🧪 QA (push) Successful in 2m13s
🚀 Build & Deploy / 🏗️ Build (push) Successful in 14m2s
🚀 Build & Deploy / 🚀 Deploy (push) Successful in 11s
🚀 Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 2m51s
🚀 Build & Deploy / 🔔 Notify (push) Successful in 2s
Nightly QA / call-qa-workflow (push) Failing after 40s
2026-04-15 00:05:45 +02:00
7109b3c32e fix: add detailed smtp logging to debug confirmation email
Some checks failed
🚀 Build & Deploy / 🔍 Prepare (push) Successful in 4s
🚀 Build & Deploy / 🧪 QA (push) Failing after 49s
🚀 Build & Deploy / 🚀 Deploy (push) Has been skipped
🚀 Build & Deploy / 🏗️ Build (push) Successful in 13m44s
🚀 Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
🚀 Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-04-14 22:28:45 +02:00
18717e7faf fix: resolve vitest hoisting issue in contact api test
Some checks failed
🚀 Build & Deploy / 🔍 Prepare (push) Successful in 7s
🚀 Build & Deploy / 🧪 QA (push) Successful in 2m9s
🚀 Build & Deploy / 🏗️ Build (push) Successful in 13m8s
🚀 Build & Deploy / 🚀 Deploy (push) Successful in 13s
🚀 Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 3m26s
🚀 Build & Deploy / 🔔 Notify (push) Successful in 2s
Nightly QA / call-qa-workflow (push) Failing after 34s
2026-04-13 22:32:16 +02:00
3478f0f42f fix: remove problematic docker maintenance step from prepare job
Some checks failed
🚀 Build & Deploy / 🔍 Prepare (push) Successful in 5s
🚀 Build & Deploy / 🧪 QA (push) Failing after 39s
🚀 Build & Deploy / 🏗️ Build (push) Successful in 12m34s
🚀 Build & Deploy / 🚀 Deploy (push) Has been skipped
🚀 Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
🚀 Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-04-13 21:45:42 +02:00
836cc66334 chore: re-trigger pipeline after network failure in #2226
Some checks failed
🚀 Build & Deploy / 🔍 Prepare (push) Failing after 13s
🚀 Build & Deploy / 🧪 QA (push) Has been skipped
🚀 Build & Deploy / 🏗️ Build (push) Has been skipped
🚀 Build & Deploy / 🚀 Deploy (push) Has been skipped
🚀 Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
🚀 Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-04-13 19:11:33 +02:00
9bb2186f7a test: update contact api test to mock nodemailer
Some checks failed
🚀 Build & Deploy / 🔍 Prepare (push) Failing after 6s
🚀 Build & Deploy / 🧪 QA (push) Has been skipped
🚀 Build & Deploy / 🏗️ Build (push) Has been skipped
🚀 Build & Deploy / 🚀 Deploy (push) Has been skipped
🚀 Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
🚀 Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-04-13 19:11:04 +02:00
55123132f4 fix(mail): bypass payload mail adapter and use standalone nodemailer
Some checks failed
🚀 Build & Deploy / 🔍 Prepare (push) Successful in 6s
🚀 Build & Deploy / 🧪 QA (push) Failing after 40s
🚀 Build & Deploy / 🏗️ Build (push) Successful in 12m4s
🚀 Build & Deploy / 🚀 Deploy (push) Has been skipped
🚀 Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
🚀 Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-04-13 17:28:19 +02:00
bf1dfe4015 fix(mail): robust recipient logic and sentry dsn verification
All checks were successful
🚀 Build & Deploy / 🔍 Prepare (push) Successful in 5s
🚀 Build & Deploy / 🧪 QA (push) Successful in 2m18s
🚀 Build & Deploy / 🏗️ Build (push) Successful in 12m3s
🚀 Build & Deploy / 🚀 Deploy (push) Successful in 14s
🚀 Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 3m58s
🚀 Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-04-13 11:49:09 +02:00
f03e417eb7 fix(prod): stabilize production environment and fix email adapter initialization
Some checks failed
🚀 Build & Deploy / 🔍 Prepare (push) Successful in 7s
🚀 Build & Deploy / 🧪 QA (push) Successful in 1m41s
🚀 Build & Deploy / 🏗️ Build (push) Successful in 12m22s
🚀 Build & Deploy / 🚀 Deploy (push) Successful in 13s
🚀 Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 3m26s
🚀 Build & Deploy / 🔔 Notify (push) Successful in 2s
Nightly QA / call-qa-workflow (push) Failing after 34s
2026-04-12 15:09:39 +02:00
e0312aeba9 chore: trigger deployment after infra disk cleanup
All checks were successful
🚀 Build & Deploy / 🔍 Prepare (push) Successful in 6s
🚀 Build & Deploy / 🧪 QA (push) Successful in 2m17s
🚀 Build & Deploy / 🏗️ Build (push) Successful in 17m23s
🚀 Build & Deploy / 🚀 Deploy (push) Successful in 13s
🚀 Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 3m17s
🚀 Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-04-12 11:29:44 +02:00
d5c0b91c97 fix(analytics): restore Umami tracking by fixing build-args and bypassing gatekeeper
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 14s
Build & Deploy / 🏗️ Build (push) Failing after 5m6s
Build & Deploy / 🧪 QA (push) Failing after 5m22s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 4s
Nightly QA / call-qa-workflow (push) Failing after 33s
2026-04-12 01:55:48 +02:00
155918e265 chore: trigger deployment after infrastructure and db fixes
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 14s
Build & Deploy / 🧪 QA (push) Successful in 8m56s
Build & Deploy / 🏗️ Build (push) Successful in 15m30s
Build & Deploy / 🚀 Deploy (push) Successful in 15s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 4m18s
Build & Deploy / 🔔 Notify (push) Successful in 2s
Nightly QA / call-qa-workflow (push) Failing after 33s
2026-04-10 14:13:24 +02:00
fb5b55f1dd fix(ci): add QEMU support for multi-arch builds (linux/amd64)
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 4s
Build & Deploy / 🧪 QA (push) Successful in 2m2s
Build & Deploy / 🏗️ Build (push) Successful in 15m12s
Build & Deploy / 🚀 Deploy (push) Failing after 1m36s
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-04-10 12:44:00 +02:00
9f00828b80 fix: restore glitchtip nesting and fix type errors
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 6s
Build & Deploy / 🧪 QA (push) Successful in 2m32s
Build & Deploy / 🏗️ Build (push) Successful in 5m10s
Build & Deploy / 🚀 Deploy (push) Successful in 42s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 4m17s
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-04-09 12:24:33 +02:00
bb0debe29d fix: correct syntax error in lib/config.ts
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 5s
Build & Deploy / 🧪 QA (push) Failing after 1m0s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🏗️ Build (push) Failing after 3m27s
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
Nightly QA / call-qa-workflow (push) Failing after 43s
2026-04-09 12:17:28 +02:00
f2e855fff9 chore: fix sentry logging instrumentation and infrastructure alignment
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 25s
Build & Deploy / 🧪 QA (push) Failing after 1m9s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🏗️ Build (push) Failing after 3m36s
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-04-09 11:32:47 +02:00
b603490683 fix(ci): add diagnostic logging and increase migration retries
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 4s
Build & Deploy / 🧪 QA (push) Successful in 2m13s
Build & Deploy / 🏗️ Build (push) Successful in 4m37s
Build & Deploy / 🚀 Deploy (push) Successful in 11s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 3m15s
Build & Deploy / 🔔 Notify (push) Successful in 1s
Nightly QA / call-qa-workflow (push) Failing after 43s
2026-03-11 21:37:56 +01:00
68075edb77 fix(ci): increase migration retry attempts to prevent flaky deployments
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 3s
Build & Deploy / 🧪 QA (push) Successful in 2m17s
Build & Deploy / 🏗️ Build (push) Successful in 4m39s
Build & Deploy / 🚀 Deploy (push) Failing after 58s
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 1s
2026-03-11 19:06:53 +01:00
217e58e5f4 feat(legal): update AGB to version 3-2026 and add support for markdown links in AGB text
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 4s
Build & Deploy / 🧪 QA (push) Successful in 5m30s
Build & Deploy / 🏗️ Build (push) Successful in 7m33s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 3m37s
Build & Deploy / 🚀 Deploy (push) Successful in 10s
Build & Deploy / 🔔 Notify (push) Successful in 1s
2026-03-11 18:54:55 +01:00
14 changed files with 186 additions and 110 deletions

View File

@@ -74,6 +74,7 @@ AUTH_COOKIE_NAME=mintel_gatekeeper_session
# Sentry / Glitchtip (Error Tracking) # Sentry / Glitchtip (Error Tracking)
SENTRY_DSN= SENTRY_DSN=
NEXT_PUBLIC_SENTRY_DSN=
# Gotify (In-App Notifications) # Gotify (In-App Notifications)
# GOTIFY_URL= # GOTIFY_URL=

View File

@@ -1,4 +1,5 @@
name: Build & Deploy # Infrastructure Maintenance: Production stabilization and email adapter fix triggered on 2026-04-12.
name: 🚀 Build & Deploy
on: on:
push: push:
@@ -36,13 +37,6 @@ jobs:
container: container:
image: catthehacker/ubuntu:act-latest image: catthehacker/ubuntu:act-latest
steps: steps:
- name: 🧹 Maintenance (High Density Cleanup)
shell: bash
run: |
echo "Purging old build layers and dangling images..."
docker image prune -f
docker builder prune -f --filter "until=6h"
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
@@ -187,6 +181,8 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: 🐳 Set up QEMU
uses: docker/setup-qemu-action@v3
- name: 🐳 Set up Docker Buildx - name: 🐳 Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: 🔐 Registry Login - name: 🔐 Registry Login
@@ -200,6 +196,8 @@ jobs:
build-args: | build-args: |
NEXT_PUBLIC_BASE_URL=${{ needs.prepare.outputs.next_public_url }} NEXT_PUBLIC_BASE_URL=${{ needs.prepare.outputs.next_public_url }}
NEXT_PUBLIC_TARGET=${{ needs.prepare.outputs.target }} NEXT_PUBLIC_TARGET=${{ needs.prepare.outputs.target }}
NEXT_PUBLIC_UMAMI_WEBSITE_ID=${{ secrets.UMAMI_WEBSITE_ID || secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID || vars.UMAMI_WEBSITE_ID || vars.NEXT_PUBLIC_UMAMI_WEBSITE_ID }}
UMAMI_API_ENDPOINT=${{ secrets.UMAMI_API_ENDPOINT || secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL || vars.UMAMI_API_ENDPOINT || 'https://analytics.infra.mintel.me' }}
NPM_TOKEN=${{ secrets.REGISTRY_PASS }} NPM_TOKEN=${{ secrets.REGISTRY_PASS }}
tags: registry.infra.mintel.me/mintel/mb-grid-solutions:${{ needs.prepare.outputs.image_tag }} tags: registry.infra.mintel.me/mintel/mb-grid-solutions:${{ needs.prepare.outputs.image_tag }}
cache-from: type=registry,ref=registry.infra.mintel.me/mintel/mb-grid-solutions:buildcache cache-from: type=registry,ref=registry.infra.mintel.me/mintel/mb-grid-solutions:buildcache
@@ -223,7 +221,7 @@ jobs:
NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }} NEXT_PUBLIC_BASE_URL: ${{ needs.prepare.outputs.next_public_url }}
# Secrets mapping (Database & CMS) # Secrets mapping (Database & CMS)
PAYLOAD_SECRET: ${{ (needs.prepare.outputs.target == 'testing' && secrets.TESTING_PAYLOAD_SECRET) || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_PAYLOAD_SECRET) || secrets.PAYLOAD_SECRET || secrets.DIRECTUS_SECRET || vars.PAYLOAD_SECRET || 'you-need-to-set-a-payload-secret' }} PAYLOAD_SECRET: ${{ (needs.prepare.outputs.target == 'testing' && secrets.TESTING_PAYLOAD_SECRET) || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_PAYLOAD_SECRET) || secrets.PAYLOAD_SECRET || secrets.MINTEL_PRIVATE_TOKEN || secrets.DIRECTUS_SECRET || vars.PAYLOAD_SECRET || 'you-need-to-set-a-payload-secret' }}
DATABASE_URI: ${{ (needs.prepare.outputs.target == 'testing' && secrets.TESTING_DATABASE_URI) || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_DATABASE_URI) || secrets.DATABASE_URI || vars.DATABASE_URI }} DATABASE_URI: ${{ (needs.prepare.outputs.target == 'testing' && secrets.TESTING_DATABASE_URI) || (needs.prepare.outputs.target == 'staging' && secrets.STAGING_DATABASE_URI) || secrets.DATABASE_URI || vars.DATABASE_URI }}
POSTGRES_DB: ${{ secrets.POSTGRES_DB || vars.POSTGRES_DB || 'payload' }} POSTGRES_DB: ${{ secrets.POSTGRES_DB || vars.POSTGRES_DB || 'payload' }}
POSTGRES_USER: ${{ secrets.POSTGRES_USER || vars.POSTGRES_USER || 'directus' }} POSTGRES_USER: ${{ secrets.POSTGRES_USER || vars.POSTGRES_USER || 'directus' }}
@@ -275,12 +273,14 @@ jobs:
GATEKEEPER_ORIGIN="$NEXT_PUBLIC_BASE_URL/gatekeeper" GATEKEEPER_ORIGIN="$NEXT_PUBLIC_BASE_URL/gatekeeper"
# Generate Environment File # Generate Environment File
echo "🔍 Verifying Sentry DSN presence..."
cat > .env.deploy << EOF cat > .env.deploy << EOF
# Generated by CI - $TARGET # Generated by CI - $TARGET
IMAGE_TAG=$IMAGE_TAG IMAGE_TAG=$IMAGE_TAG
NEXT_PUBLIC_BASE_URL=$NEXT_PUBLIC_BASE_URL NEXT_PUBLIC_BASE_URL=$NEXT_PUBLIC_BASE_URL
GATEKEEPER_ORIGIN=$GATEKEEPER_ORIGIN GATEKEEPER_ORIGIN=$GATEKEEPER_ORIGIN
SENTRY_DSN=$SENTRY_DSN SENTRY_DSN=$SENTRY_DSN
NEXT_PUBLIC_SENTRY_DSN=$SENTRY_DSN
PROJECT_COLOR=$PROJECT_COLOR PROJECT_COLOR=$PROJECT_COLOR
LOG_LEVEL=$LOG_LEVEL LOG_LEVEL=$LOG_LEVEL
@@ -341,11 +341,24 @@ jobs:
# Apply Payload Migrations using the target app container's programmatic endpoint # Apply Payload Migrations using the target app container's programmatic endpoint
ssh root@alpha.mintel.me "cd $SITE_DIR && echo '→ Waiting for DB and Running Payload Migrations...' && \ ssh root@alpha.mintel.me "cd $SITE_DIR && echo '→ Waiting for DB and Running Payload Migrations...' && \
for i in {1..5}; do \ for i in {1..15}; do \
echo \"Attempt \$i...\"; \ echo \"Attempt \$i...\"; \
docker compose -p '${{ needs.prepare.outputs.project_name }}' --env-file '$ENV_FILE' exec -T mb-grid-app sh -c 'curl -s -f -X POST -H \"Authorization: Bearer \$PAYLOAD_SECRET\" http://localhost:3000/api/payload/migrate \ docker compose -p '${{ needs.prepare.outputs.project_name }}' --env-file '$ENV_FILE' exec -T mb-grid-app sh -c 'curl -s -i -X POST -H \"Authorization: Bearer \$PAYLOAD_SECRET\" http://localhost:3000/api/payload/migrate' > /tmp/migrate_res 2>&1; \
|| { echo \"HTTP error or DB not ready.\"; exit 1; }' && { echo '✅ Migrations successful!'; break; } \ if grep -q \"200 OK\" /tmp/migrate_res; then \
|| { if [ \$i -eq 5 ]; then echo '❌ Migration failed after 5 attempts!'; exit 1; else echo '⏳ Retrying in 5s...'; sleep 5; fi; }; \ echo \"✅ Migrations successful!\"; \
cat /tmp/migrate_res; \
break; \
else \
echo \"❌ Attempt \$i failed. Response:\"; \
cat /tmp/migrate_res; \
if [ \$i -eq 15 ]; then \
echo \"❌ Migration failed after 15 attempts! Dumping app logs...\"; \
docker compose -p '${{ needs.prepare.outputs.project_name }}' --env-file '$ENV_FILE' logs --tail 100 mb-grid-app; \
exit 1; \
fi; \
echo \"⏳ Retrying in 5s...\"; \
sleep 5; \
fi; \
done" done"
ssh root@alpha.mintel.me "docker system prune -f --filter 'until=24h'" ssh root@alpha.mintel.me "docker system prune -f --filter 'until=24h'"

View File

@@ -74,7 +74,7 @@ export default function AGB() {
<p className="text-slate-500 font-medium">{stand}</p> <p className="text-slate-500 font-medium">{stand}</p>
</div> </div>
<a <a
href="/assets/AGB MB Grid 1-2026.pdf" href="/assets/AGB MB Grid 3-2026.pdf"
download download
className="btn-primary !py-3 !px-6 flex items-center gap-2" className="btn-primary !py-3 !px-6 flex items-center gap-2"
> >
@@ -91,7 +91,24 @@ export default function AGB() {
</h2> </h2>
<div className="space-y-4"> <div className="space-y-4">
{section.content.map((paragraph, pIndex) => ( {section.content.map((paragraph, pIndex) => (
<p key={pIndex}>{paragraph}</p> <p key={pIndex}>
{paragraph.split(/(\[.*?\]\(.*?\))/g).map((part, i) => {
const match = part.match(/\[(.*?)\]\((.*?)\)/);
if (match) {
return (
<a
key={i}
href={match[2]}
className="text-primary hover:underline font-medium"
download={match[2].endsWith(".pdf")}
>
{match[1]}
</a>
);
}
return part;
})}
</p>
))} ))}
</div> </div>
</div> </div>

View File

@@ -8,6 +8,7 @@ import {
ConfirmationMessage, ConfirmationMessage,
} from "@mintel/mail"; } from "@mintel/mail";
import React from "react"; import React from "react";
import nodemailer from "nodemailer";
export async function POST(req: Request) { export async function POST(req: Request) {
const services = getServerAppServices(); const services = getServerAppServices();
@@ -77,11 +78,33 @@ export async function POST(req: Request) {
services.errors.captureException(payloadError, { phase: "payload_save" }); services.errors.captureException(payloadError, { phase: "payload_save" });
} }
// 2. Email sending via Payload (which uses configured nodemailer) // 2. Email sending via standalone Nodemailer (bypassing Payload adapter)
try { try {
const { config } = await import("@/lib/config"); const { config } = await import("@/lib/config");
const clientName = "MB Grid Solutions"; const clientName = "MB Grid Solutions";
// Robust recipient resolution
const recipients = Array.isArray(config.mail.recipients)
? config.mail.recipients.filter(Boolean)
: [];
const to =
recipients.length > 0
? recipients.join(",")
: process.env.CONTACT_RECIPIENT || "info@mb-grid-solutions.com";
logger.info("Instantiating standalone nodemailer transport");
const transporter = nodemailer.createTransport({
host: config.mail.host,
port: config.mail.port,
auth: {
user: config.mail.user,
pass: config.mail.pass,
},
});
logger.info("Preparing to send notification email", { to, host: config.mail.host });
// 2a. Notification to MB Grid // 2a. Notification to MB Grid
const notificationHtml = await render( const notificationHtml = await render(
React.createElement(ContactFormNotification, { React.createElement(ContactFormNotification, {
@@ -92,19 +115,23 @@ export async function POST(req: Request) {
}), }),
); );
await payload.sendEmail({ try {
const info = await transporter.sendMail({
from: config.mail.from, from: config.mail.from,
to: to,
config.mail.recipients.join(",") ||
process.env.CONTACT_RECIPIENT ||
"info@mb-grid-solutions.com",
replyTo: email, replyTo: email,
subject: `Kontaktanfrage von ${name}`, subject: `Kontaktanfrage von ${name}`,
html: notificationHtml, html: notificationHtml,
}); });
logger.info("Notification email sent successfully", { messageId: info?.messageId });
} catch (notifyError) {
logger.error("Failed to send notification email", { error: notifyError });
throw notifyError; // Re-throw to be caught by the outer SMTP catch
}
// 2b. Confirmation to the User // 2b. Confirmation to the User
try { try {
logger.info("Preparing to send confirmation email", { to: email });
const confirmationHtml = await render( const confirmationHtml = await render(
React.createElement(ConfirmationMessage, { React.createElement(ConfirmationMessage, {
name, name,
@@ -112,16 +139,17 @@ export async function POST(req: Request) {
}), }),
); );
await payload.sendEmail({ const info = await transporter.sendMail({
from: config.mail.from, from: config.mail.from,
to: email, to: email,
subject: `Ihre Kontaktanfrage bei ${clientName}`, subject: `Ihre Kontaktanfrage bei ${clientName}`,
html: confirmationHtml, html: confirmationHtml,
}); });
logger.info("Confirmation email sent successfully", { messageId: info?.messageId });
} catch (confirmError) { } catch (confirmError) {
logger.warn( logger.warn(
"Failed to send confirmation email, but notification was sent", "Failed to send confirmation email, but notification was sent",
{ error: confirmError }, { error: confirmError instanceof Error ? confirmError.message : String(confirmError) },
); );
} }

View File

@@ -1,6 +1,5 @@
Liefer- und Zahlungsbedingungen Liefer- und Zahlungsbedingungen
Stand Januar 2026 Stand März 2026
1. Allgemeines 1. Allgemeines
Diese Liefer- und Zahlungsbedingungen (L&Z) der MB Grid Solutions & Services gelten ausschließlich; entgegenstehende oder von unseren Bedingungen abweichende Bedingungen des Kunden erkennen wir nicht an, es sei denn, wir hätten ausdrücklich schriftlich ihrer Geltung zugestimmt. Unsere L&Z gelten auch dann, wenn wir in Kenntnis entgegenstehender oder von unseren L&Z abweichender Bedingungen des Bestellers die Lieferung an diesen vorbehaltlos ausführen. Unsere L&Z gelten nur gegenüber Unternehmern im Sinn von § 310 Abs. 1 BGB sowie juristischen Personen des öffentlichen Rechts oder öffentlich-rechtliches Sondervermögen. Diese Liefer- und Zahlungsbedingungen (L&Z) der MB Grid Solutions & Services gelten ausschließlich; entgegenstehende oder von unseren Bedingungen abweichende Bedingungen des Kunden erkennen wir nicht an, es sei denn, wir hätten ausdrücklich schriftlich ihrer Geltung zugestimmt. Unsere L&Z gelten auch dann, wenn wir in Kenntnis entgegenstehender oder von unseren L&Z abweichender Bedingungen des Bestellers die Lieferung an diesen vorbehaltlos ausführen. Unsere L&Z gelten nur gegenüber Unternehmern im Sinn von § 310 Abs. 1 BGB sowie juristischen Personen des öffentlichen Rechts oder öffentlich-rechtliches Sondervermögen.
@@ -74,7 +73,7 @@ Sollte es bei einer Mängelrüge zu unterschiedlichen Meinungen bezüglich des K
Wir haften unbeschränkt nur für Vorsatz und grobe Fahrlässigkeit sowie für Schäden aus einer Verletzung von Leben, Körper oder Gesundheit, die auf mindestens fahrlässiger Pflichtverletzung unsererseits oder unserer gesetzlichen Vertreter oder Erfüllungsgehilfen beruhen; ebenso haften wir unbeschränkt im Fall von uns übernommenen bzw. abgegebenen Garantien und Zusicherungen, sofern ein davon umfasster Mangel unsere Haftung auslöst sowie im Fall einer Haftung nach dem Produkthaftungsgesetz oder sonstigen Gefährdungshaftungstatbeständen. Im Fall sonstiger schuldhafter Verletzung wesentlicher Vertragspflichten („Kardinalpflichten“) ist unsere verbleibende Haftung auf den vertragstypischen vorhersehbaren Schaden beschränkt. Mangelfolgeschäden sowie entgangener Gewinn schließen wir grundsätzlich aus. Wir haften unbeschränkt nur für Vorsatz und grobe Fahrlässigkeit sowie für Schäden aus einer Verletzung von Leben, Körper oder Gesundheit, die auf mindestens fahrlässiger Pflichtverletzung unsererseits oder unserer gesetzlichen Vertreter oder Erfüllungsgehilfen beruhen; ebenso haften wir unbeschränkt im Fall von uns übernommenen bzw. abgegebenen Garantien und Zusicherungen, sofern ein davon umfasster Mangel unsere Haftung auslöst sowie im Fall einer Haftung nach dem Produkthaftungsgesetz oder sonstigen Gefährdungshaftungstatbeständen. Im Fall sonstiger schuldhafter Verletzung wesentlicher Vertragspflichten („Kardinalpflichten“) ist unsere verbleibende Haftung auf den vertragstypischen vorhersehbaren Schaden beschränkt. Mangelfolgeschäden sowie entgangener Gewinn schließen wir grundsätzlich aus.
16. Kabeltrommeln 16. Kabeltrommeln
Unsere Kabel werden auf stabilen Vollholztrommeln geliefert. Auf Wunsch vermitteln wir Ihnen Partner, die diese Trommeln gegen eine Gebühr abholen. Unsere Kabel werden auf stabilen Vollholztrommeln geliefert. Bei Lagerzeiten über 3 Monaten ist zu beachten, dass die Trommel regelmäßig bewegt wird, um eine einseitige Belastung zu vermeiden. Folgeschäden bei Nichtbeachtung wie Instabilität schließen wir aus. Zusätzlich die [PDF zum Download](/assets/AGB MB Grid 3-2026.pdf). Auf Wunsch vermitteln wir Ihnen Partner, die diese Trommeln gegen eine Gebühr abholen.
17. Technische Beratungsdienstleistungen 17. Technische Beratungsdienstleistungen
Die technische Unterstützung ersetzt weder die Fachplanung noch die Ausführungs- oder Prüfverantwortung des beauftragten Ingenieurbüros, Planers oder der ausführenden Fachfirma bzw. verantwortlichen Abteilung. Die technische Unterstützung ersetzt weder die Fachplanung noch die Ausführungs- oder Prüfverantwortung des beauftragten Ingenieurbüros, Planers oder der ausführenden Fachfirma bzw. verantwortlichen Abteilung.

View File

@@ -20,7 +20,7 @@ services:
- "traefik.docker.network=infra" - "traefik.docker.network=infra"
# Public Router paths that bypass Gatekeeper auth # Public Router paths that bypass Gatekeeper auth
- "traefik.http.routers.${PROJECT_NAME:-mb-grid}-public.rule=Host(`${TRAEFIK_HOST:-mb-grid-solutions.localhost}`) && PathRegexp(`^/([a-z]{2}/)?(health|login|gatekeeper|uploads|media|robots\\.txt|manifest\\.webmanifest|sitemap(-[0-9]+)?\\.xml|(.*/)?api/og(/.*)?|(.*/)?opengraph-image.*)`)" - "traefik.http.routers.${PROJECT_NAME:-mb-grid}-public.rule=Host(`${TRAEFIK_HOST:-mb-grid-solutions.localhost}`) && PathRegexp(`^/([a-z]{2}/)?(health|login|gatekeeper|uploads|media|robots\\.txt|manifest\\.webmanifest|sitemap(-[0-9]+)?\\.xml|(.*/)?api/og(/.*)?|(.*/)?opengraph-image.*|stats(/.*)?|errors(/.*)?)`)"
- "traefik.http.routers.${PROJECT_NAME:-mb-grid}-public.entrypoints=websecure" - "traefik.http.routers.${PROJECT_NAME:-mb-grid}-public.entrypoints=websecure"
- "traefik.http.routers.${PROJECT_NAME:-mb-grid}-public.tls.certresolver=le" - "traefik.http.routers.${PROJECT_NAME:-mb-grid}-public.tls.certresolver=le"
- "traefik.http.routers.${PROJECT_NAME:-mb-grid}-public.tls=true" - "traefik.http.routers.${PROJECT_NAME:-mb-grid}-public.tls=true"

10
instrumentation.ts Normal file
View File

@@ -0,0 +1,10 @@
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('./sentry.server.config');
}
if (process.env.NEXT_RUNTIME === 'edge') {
await import('./sentry.edge.config');
}
}

View File

@@ -37,8 +37,8 @@ function createConfig() {
errors: { errors: {
glitchtip: { glitchtip: {
// Use SENTRY_DSN for both server and client (proxied) // Use NEXT_PUBLIC_SENTRY_DSN if available (required for browser-side SDK)
dsn: env.SENTRY_DSN, dsn: env.NEXT_PUBLIC_SENTRY_DSN || env.SENTRY_DSN,
// The proxied origin used in the frontend // The proxied origin used in the frontend
proxyPath: "/errors", proxyPath: "/errors",
enabled: Boolean(env.SENTRY_DSN), enabled: Boolean(env.SENTRY_DSN),

View File

@@ -17,6 +17,8 @@ import {
const envExtension = { const envExtension = {
// Project specific overrides or additions // Project specific overrides or additions
AUTH_COOKIE_NAME: z.string().default("mb_gatekeeper_session"), AUTH_COOKIE_NAME: z.string().default("mb_gatekeeper_session"),
NEXT_PUBLIC_SENTRY_DSN: z.string().optional(),
SENTRY_DSN: z.string().optional(),
}; };
/** /**

View File

@@ -1,6 +1,8 @@
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import createMiddleware from "next-intl/middleware"; import createMiddleware from "next-intl/middleware";
export default createMiddleware({ const intlMiddleware = createMiddleware({
// A list of all locales that are supported // A list of all locales that are supported
locales: ["de"], locales: ["de"],
@@ -11,6 +13,17 @@ export default createMiddleware({
localePrefix: "as-needed", localePrefix: "as-needed",
}); });
export default function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Explicit bypass for analytics and error tracking paths
if (pathname.startsWith("/stats") || pathname.startsWith("/errors")) {
return NextResponse.next();
}
return intlMiddleware(request);
}
export const config = { export const config = {
// Matcher for all pages and internationalized pathnames // Matcher for all pages and internationalized pathnames
// excluding api, _next, static files, etc. // excluding api, _next, static files, etc.

View File

@@ -36,7 +36,6 @@
"@payloadcms/ui": "^3.77.0", "@payloadcms/ui": "^3.77.0",
"@react-email/components": "^1.0.8", "@react-email/components": "^1.0.8",
"@sentry/nextjs": "^10.38.0", "@sentry/nextjs": "^10.38.0",
"bcrypt": "^6.0.0",
"bcryptjs": "^3.0.3", "bcryptjs": "^3.0.3",
"framer-motion": "^12.29.2", "framer-motion": "^12.29.2",
"graphql": "^16.13.0", "graphql": "^16.13.0",

44
pnpm-lock.yaml generated
View File

@@ -44,9 +44,6 @@ importers:
'@sentry/nextjs': '@sentry/nextjs':
specifier: ^10.38.0 specifier: ^10.38.0
version: 10.38.0(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.77.4))(react@19.2.4)(webpack@5.104.1(esbuild@0.25.12)) version: 10.38.0(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.77.4))(react@19.2.4)(webpack@5.104.1(esbuild@0.25.12))
bcrypt:
specifier: ^6.0.0
version: 6.0.0
bcryptjs: bcryptjs:
specifier: ^3.0.3 specifier: ^3.0.3
version: 3.0.3 version: 3.0.3
@@ -3488,10 +3485,6 @@ packages:
resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==}
hasBin: true hasBin: true
bcrypt@6.0.0:
resolution: {integrity: sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==}
engines: {node: '>= 18'}
bcryptjs@3.0.3: bcryptjs@3.0.3:
resolution: {integrity: sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==} resolution: {integrity: sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==}
hasBin: true hasBin: true
@@ -5467,10 +5460,6 @@ packages:
node-addon-api@7.1.1: node-addon-api@7.1.1:
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
node-addon-api@8.6.0:
resolution: {integrity: sha512-gBVjCaqDlRUk0EwoPNKzIr9KkS9041G/q31IBShPs1Xz6UTA+EXdZADbzqAJQrpDRq71CIMnOP5VMut3SL0z5Q==}
engines: {node: ^18 || ^20 || >= 21}
node-fetch@2.7.0: node-fetch@2.7.0:
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
engines: {node: 4.x || >=6.0.0} engines: {node: 4.x || >=6.0.0}
@@ -5480,10 +5469,6 @@ packages:
encoding: encoding:
optional: true optional: true
node-gyp-build@4.8.4:
resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
hasBin: true
node-releases@2.0.27: node-releases@2.0.27:
resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
@@ -10779,11 +10764,6 @@ snapshots:
baseline-browser-mapping@2.9.19: {} baseline-browser-mapping@2.9.19: {}
bcrypt@6.0.0:
dependencies:
node-addon-api: 8.6.0
node-gyp-build: 4.8.4
bcryptjs@3.0.3: {} bcryptjs@3.0.3: {}
bidi-js@1.0.3: bidi-js@1.0.3:
@@ -11509,8 +11489,8 @@ snapshots:
'@typescript-eslint/parser': 8.54.0(eslint@8.57.1)(typescript@5.9.3) '@typescript-eslint/parser': 8.54.0(eslint@8.57.1)(typescript@5.9.3)
eslint: 8.57.1 eslint: 8.57.1
eslint-import-resolver-node: 0.3.9 eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1) eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1)
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1)
eslint-plugin-react: 7.37.5(eslint@8.57.1) eslint-plugin-react: 7.37.5(eslint@8.57.1)
eslint-plugin-react-hooks: 5.2.0(eslint@8.57.1) eslint-plugin-react-hooks: 5.2.0(eslint@8.57.1)
@@ -11526,8 +11506,8 @@ snapshots:
'@next/eslint-plugin-next': 16.1.6 '@next/eslint-plugin-next': 16.1.6
eslint: 8.57.1 eslint: 8.57.1
eslint-import-resolver-node: 0.3.9 eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1) eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1)
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1)
eslint-plugin-react: 7.37.5(eslint@8.57.1) eslint-plugin-react: 7.37.5(eslint@8.57.1)
eslint-plugin-react-hooks: 7.0.1(eslint@8.57.1) eslint-plugin-react-hooks: 7.0.1(eslint@8.57.1)
@@ -11549,7 +11529,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1): eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1):
dependencies: dependencies:
'@nolyfill/is-core-module': 1.0.39 '@nolyfill/is-core-module': 1.0.39
debug: 4.4.3 debug: 4.4.3
@@ -11560,22 +11540,22 @@ snapshots:
tinyglobby: 0.2.15 tinyglobby: 0.2.15
unrs-resolver: 1.11.1 unrs-resolver: 1.11.1
optionalDependencies: optionalDependencies:
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): eslint-module-utils@2.12.1(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
dependencies: dependencies:
debug: 3.2.7 debug: 3.2.7
optionalDependencies: optionalDependencies:
'@typescript-eslint/parser': 8.54.0(eslint@8.57.1)(typescript@5.9.3) '@typescript-eslint/parser': 8.54.0(eslint@8.57.1)(typescript@5.9.3)
eslint: 8.57.1 eslint: 8.57.1
eslint-import-resolver-node: 0.3.9 eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1) eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
dependencies: dependencies:
'@rtsao/scc': 1.1.0 '@rtsao/scc': 1.1.0
array-includes: 3.1.9 array-includes: 3.1.9
@@ -11586,7 +11566,7 @@ snapshots:
doctrine: 2.1.0 doctrine: 2.1.0
eslint: 8.57.1 eslint: 8.57.1
eslint-import-resolver-node: 0.3.9 eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
hasown: 2.0.2 hasown: 2.0.2
is-core-module: 2.16.1 is-core-module: 2.16.1
is-glob: 4.0.3 is-glob: 4.0.3
@@ -13015,14 +12995,10 @@ snapshots:
node-addon-api@7.1.1: {} node-addon-api@7.1.1: {}
node-addon-api@8.6.0: {}
node-fetch@2.7.0: node-fetch@2.7.0:
dependencies: dependencies:
whatwg-url: 5.0.0 whatwg-url: 5.0.0
node-gyp-build@4.8.4: {}
node-releases@2.0.27: {} node-releases@2.0.27: {}
nodemailer@7.0.12: {} nodemailer@7.0.12: {}

View File

@@ -50,24 +50,29 @@ export default buildConfig({
}, },
prodMigrations: migrations, prodMigrations: migrations,
}), }),
...(process.env.SMTP_HOST
? {
email: nodemailerAdapter({ email: nodemailerAdapter({
defaultFromAddress: defaultFromAddress:
process.env.SMTP_FROM || "info@mb-grid-solutions.com", process.env.MAIL_FROM ||
process.env.SMTP_FROM ||
"info@mb-grid-solutions.com",
defaultFromName: "MB Grid Solutions CMS", defaultFromName: "MB Grid Solutions CMS",
transportOptions: { transportOptions: {
host: process.env.SMTP_HOST, host: process.env.MAIL_HOST || process.env.SMTP_HOST || "localhost",
port: parseInt(process.env.SMTP_PORT || "587"), port: parseInt(
process.env.MAIL_PORT || process.env.SMTP_PORT || "587",
),
auth: { auth: {
user: process.env.SMTP_USER, user:
pass: process.env.SMTP_PASS, process.env.MAIL_USERNAME ||
process.env.MAIL_USER ||
process.env.SMTP_USER,
pass: process.env.MAIL_PASSWORD || process.env.SMTP_PASS,
}, },
secure: process.env.SMTP_SECURE === "true", secure:
process.env.MAIL_SECURE === "true" ||
process.env.SMTP_SECURE === "true",
}, },
}), }),
}
: {}),
sharp, sharp,
plugins: [ plugins: [
...(process.env.S3_ENDPOINT ...(process.env.S3_ENDPOINT

View File

@@ -6,6 +6,19 @@ const { mockCreate, mockSendEmail } = vi.hoisted(() => ({
mockSendEmail: vi.fn(), mockSendEmail: vi.fn(),
})); }));
// Mock Nodemailer
const { mockSendMail } = vi.hoisted(() => ({
mockSendMail: vi.fn().mockResolvedValue({ messageId: "mock-message-id" }),
}));
vi.mock("nodemailer", () => ({
default: {
createTransport: vi.fn().mockReturnValue({
sendMail: mockSendMail,
}),
},
}));
vi.mock("payload", () => ({ vi.mock("payload", () => ({
getPayload: vi.fn().mockResolvedValue({ getPayload: vi.fn().mockResolvedValue({
create: mockCreate, create: mockCreate,
@@ -101,7 +114,7 @@ describe("Contact API Integration", () => {
// But it actually does NOTHING internally // But it actually does NOTHING internally
expect(mockCreate).not.toHaveBeenCalled(); expect(mockCreate).not.toHaveBeenCalled();
expect(mockSendEmail).not.toHaveBeenCalled(); expect(mockSendMail).not.toHaveBeenCalled();
}); });
it("should successfully save to Payload and send emails", async () => { it("should successfully save to Payload and send emails", async () => {
@@ -140,10 +153,10 @@ describe("Contact API Integration", () => {
}); });
// 2. Verify Email Sending // 2. Verify Email Sending
// Note: sendEmail is called twice (Notification + User Confirmation) // Note: sendMail is called twice (Notification + User Confirmation)
expect(mockSendEmail).toHaveBeenCalledTimes(2); expect(mockSendMail).toHaveBeenCalledTimes(2);
expect(mockSendEmail).toHaveBeenNthCalledWith( expect(mockSendMail).toHaveBeenNthCalledWith(
1, 1,
expect.objectContaining({ expect.objectContaining({
subject: "Kontaktanfrage von Jane Doe", subject: "Kontaktanfrage von Jane Doe",
@@ -151,7 +164,7 @@ describe("Contact API Integration", () => {
}), }),
); );
expect(mockSendEmail).toHaveBeenNthCalledWith( expect(mockSendMail).toHaveBeenNthCalledWith(
2, 2,
expect.objectContaining({ expect.objectContaining({
to: "jane@example.com", to: "jane@example.com",