Compare commits

..

2 Commits

Author SHA1 Message Date
DanielS
0ec1319753 style(mail): humanize email templates and remove corporate boilerplates
All checks were successful
Staging Build / build (push) Successful in 2m39s
2026-07-07 00:39:06 +02:00
DanielS
057012e09f chore(supabase): remove unused pos_modules and licenses tables 2026-07-07 00:23:54 +02:00
3 changed files with 142 additions and 19 deletions

View File

@@ -8,3 +8,12 @@
- Die ungültige RLS-Policy auf `public.partners` wurde entfernt.
- `public.companies` wird stattdessen korrekt durch `Admin manage companies` geschützt.
- **Consequences**: Behebt Migrations-Fehler im Staging-Server, CI/CD-Pipeline baut wieder fehlerfrei.
## ADR-002: Entfernung ungenutzter Lizenztabellen
- **Status**: Accepted
- **Date**: 2026-07-06
- **Context**: Die Tabellen `pos_modules` und `licenses` wurden von einem alten bzw. externen Service genutzt. Im aktuellen Webshop-Frontend werden sie nicht benötigt und führen zu unnötiger RLS- und Integritäts-Komplexität.
- **Decision**:
- Erstellen einer SQL-Migration zum Löschen (`DROP`) von `licenses` und `pos_modules`.
- Anpassung der Integritätsprüfungs-Funktionen `check_database_integrity` und `repair_database_schema` zur Entfernung dieser Relationen.
- **Consequences**: Schlankere Datenbankstruktur, RLS-Komplexität verringert und sauberere Codebasis.

View File

@@ -11,12 +11,12 @@ export function getOrderEmailTemplate(
siteUrl: string,
isUpdate: boolean = false
) {
const title = isUpdate ? 'Ihre Anfrage wurde aktualisiert!' : 'Vielen Dank für Ihre Anfrage!'
const title = isUpdate ? 'Anfrage geändert' : 'Anfrage erhalten'
const intro = isUpdate
? `Ihr Anfrage mit der Nummer ${details.orderNumber} wurde erfolgreich aktualisiert.`
: 'wir freuen uns sehr über Ihre Anfrage bei CASPOS Shop. Ihre Anfrage wurde erfolgreich entgegengenommen und wird nun verarbeitet.'
? `Deine Anfrage ${details.orderNumber} wurde aktualisiert.`
: `Deine Anfrage ${details.orderNumber} ist bei uns eingegangen und wird bearbeitet.`
const text = `Hallo,\n\n${isUpdate ? 'ihre Anfrage wurde erfolgreich aktualisiert!' : 'vielen Dank für Ihre Anfrage bei CASPOS Shop!'}\n\n${isUpdate ? 'Neue Anfragedetails' : 'Anfragedetails'}:\n- Anfrage-Nummer: ${details.orderNumber}\n- Datum: ${details.formattedDate}\n- Endkunde: ${details.customerCompanyName}\n${details.totalDetailsText}\n\nIm Anhang dieser E-Mail finden Sie Ihre ${isUpdate ? 'aktualisierte' : ''} Anfragebestätigung als PDF-Dokument.\n\nPDF herunterladen: ${siteUrl}/api/orders/${details.orderNumber}/download\n\nViele Grüße,\nIhr CASPOS Shop-Team`
const text = `Hallo,\n\n${isUpdate ? 'Deine Anfrage wurde aktualisiert.' : 'Deine Anfrage ist bei uns eingegangen.'}\n\nDetails:\n- Nummer: ${details.orderNumber}\n- Datum: ${details.formattedDate}\n- Kunde: ${details.customerCompanyName}\n${details.totalDetailsText}\n\nDeine Anfragebestätigung findest du im Anhang als PDF.\n\nPDF-Link: ${siteUrl}/api/orders/${details.orderNumber}/download\n\nViele Grüße,\nDein CASPOS Team`
const html = `
<div style="font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #e2e8f0; border-radius: 8px;">
@@ -24,10 +24,10 @@ export function getOrderEmailTemplate(
<p style="color: #475569; font-size: 16px; line-height: 1.5;">Hallo,</p>
<p style="color: #475569; font-size: 16px; line-height: 1.5;">${intro}</p>
<div style="background-color: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; padding: 16px; margin: 24px 0;">
<h3 style="color: #0f172a; margin-top: 0; margin-bottom: 12px;">${isUpdate ? 'Neue Anfrage-Details' : 'Anfrage-Details'}</h3>
<h3 style="color: #0f172a; margin-top: 0; margin-bottom: 12px;">Details</h3>
<table style="width: 100%; border-collapse: collapse; font-size: 14px; color: #475569;">
<tr>
<td style="padding: 4px 0; font-weight: bold; width: 140px;">Anfrage-Nummer:</td>
<td style="padding: 4px 0; font-weight: bold; width: 140px;">Nummer:</td>
<td style="padding: 4px 0;">${details.orderNumber}</td>
</tr>
<tr>
@@ -35,18 +35,17 @@ export function getOrderEmailTemplate(
<td style="padding: 4px 0;">${details.formattedDate}</td>
</tr>
<tr>
<td style="padding: 4px 0; font-weight: bold;">Endkunde:</td>
<td style="padding: 4px 0; font-weight: bold;">Kunde:</td>
<td style="padding: 4px 0;">${details.customerCompanyName}</td>
</tr>
${details.totalDetailsHtml}
</table>
</div>
<p style="color: #475569; font-size: 16px; line-height: 1.5;">Im Anhang dieser E-Mail finden Sie Ihre ${isUpdate ? 'aktualisierte' : ''} Anfragebestätigung als PDF-Dokument.</p>
<p style="color: #475569; font-size: 16px; line-height: 1.5;">Die Bestätigung liegt als PDF im Anhang.</p>
<div style="text-align: center; margin: 24px 0;">
<a href="${siteUrl}" style="display: inline-block; background-color: #3b82f6; color: #ffffff; text-decoration: none; padding: 12px 24px; border-radius: 6px; font-weight: bold; font-size: 14px;">📄 PDF herunterladen</a>
</div>
<p style="color: #475569; font-size: 16px; line-height: 1.5; margin-top: 24px;">Mit freundlichen Grüßen,<br>Ihr CASPOS Shop-Team</p>
${!isUpdate ? '<hr style="border: 0; border-top: 1px solid #e2e8f0; margin: 24px 0;"><p style="color: #94a3b8; font-size: 12px; text-align: center;">Diese E-Mail wurde automatisch generiert. Bitte antworten Sie nicht darauf.</p>' : ''}
<p style="color: #475569; font-size: 16px; line-height: 1.5; margin-top: 24px;">Viele Grüße,<br>Dein CASPOS Team</p>
</div>
`
@@ -54,32 +53,30 @@ export function getOrderEmailTemplate(
}
export function getStatusEmailTemplate(orderNumber: string, oldLabel: string, newLabel: string) {
const text = `Hallo,\n\nder Status Ihrer Bestellung mit der Nummer ${orderNumber} hat sich geändert.\n\nNeuer Status: ${newLabel} (vorher: ${oldLabel})\n\nViele Grüße,\nIhr CASPOS Shop-Team`
const text = `Hallo,\n\nder Status deiner Anfrage ${orderNumber} hat sich geändert.\n\nStatus: ${newLabel} (vorher: ${oldLabel})\n\nViele Grüße,\nDein CASPOS Team`
const html = `
<div style="font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #e2e8f0; border-radius: 8px;">
<h2 style="color: #0f172a; margin-bottom: 16px;">Status Ihrer Bestellung hat sich geändert</h2>
<h2 style="color: #0f172a; margin-bottom: 16px;">Statusänderung</h2>
<p style="color: #475569; font-size: 16px; line-height: 1.5;">Hallo,</p>
<p style="color: #475569; font-size: 16px; line-height: 1.5;">der Status Ihrer Bestellung mit der Nummer <strong>${orderNumber}</strong> wurde aktualisiert.</p>
<p style="color: #475569; font-size: 16px; line-height: 1.5;">der Status deiner Anfrage <strong>${orderNumber}</strong> wurde aktualisiert.</p>
<div style="background-color: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; padding: 16px; margin: 24px 0;">
<table style="width: 100%; border-collapse: collapse; font-size: 14px; color: #475569;">
<tr>
<td style="padding: 4px 0; font-weight: bold; width: 140px;">Bestellnummer:</td>
<td style="padding: 4px 0; font-weight: bold; width: 140px;">Nummer:</td>
<td style="padding: 4px 0;">${orderNumber}</td>
</tr>
<tr>
<td style="padding: 4px 0; font-weight: bold;">Alter Status:</td>
<td style="padding: 4px 0; font-weight: bold;">Vorher:</td>
<td style="padding: 4px 0; text-decoration: line-through; color: #94a3b8;">${oldLabel}</td>
</tr>
<tr>
<td style="padding: 4px 0; font-weight: bold;">Neuer Status:</td>
<td style="padding: 4px 0; font-weight: bold;">Aktuell:</td>
<td style="padding: 4px 0; font-weight: bold; color: #3b82f6;">${newLabel}</td>
</tr>
</table>
</div>
<p style="color: #475569; font-size: 16px; line-height: 1.5;">Viele Grüße,<br>Ihr CASPOS Shop-Team</p>
<hr style="border: 0; border-top: 1px solid #e2e8f0; margin: 24px 0;">
<p style="color: #94a3b8; font-size: 12px; text-align: center;">Diese E-Mail wurde automatisch generiert. Bitte antworten Sie nicht darauf.</p>
<p style="color: #475569; font-size: 16px; line-height: 1.5;">Viele Grüße,<br>Dein CASPOS Team</p>
</div>
`

View File

@@ -0,0 +1,117 @@
-- Migration: Remove unused pos_modules and licenses tables and reference functions
-- 1. Drop the tables (cascade to clean up RLS policies automatically)
DROP TABLE IF EXISTS public.licenses CASCADE;
DROP TABLE IF EXISTS public.pos_modules CASCADE;
-- 2. Update database integrity function to remove pos_modules and licenses checks
CREATE OR REPLACE FUNCTION public.check_database_integrity()
RETURNS jsonb
SECURITY DEFINER
AS $$
DECLARE
tables_to_check text[] := ARRAY[
'users',
'settings',
'companies',
'categories',
'products',
'product_modules',
'orders',
'profiles',
'end_customers'
];
t text;
t_exists boolean;
t_count bigint;
tables_result jsonb := jsonb_build_object();
result jsonb;
BEGIN
FOREACH t IN ARRAY tables_to_check LOOP
SELECT EXISTS (
SELECT 1 FROM pg_tables WHERE schemaname = 'public' AND tablename = t
) INTO t_exists;
t_count := 0;
IF t_exists THEN
EXECUTE format('SELECT count(*) FROM public.%I', t) INTO t_count;
END IF;
tables_result := tables_result || jsonb_build_object(t, jsonb_build_object('exists', t_exists, 'count', t_count));
END LOOP;
result := jsonb_build_object(
'tables', tables_result,
'errors', jsonb_build_object(
'foreign_keys', 0
)
);
RETURN result;
END;
$$ LANGUAGE plpgsql;
-- 3. Update database repair schema function to remove creation of pos_modules and licenses
CREATE OR REPLACE FUNCTION public.repair_database_schema()
RETURNS jsonb
SECURITY DEFINER
AS $$
DECLARE
repaired_tables text[] := '{}';
result jsonb;
BEGIN
-- 1. profiles table
IF NOT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'public' AND tablename = 'profiles') THEN
CREATE TABLE public.profiles (
id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
first_name TEXT,
last_name TEXT,
company_name TEXT,
vat_id TEXT,
address TEXT,
city TEXT,
zip_code TEXT,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL
);
repaired_tables := array_append(repaired_tables, 'profiles');
END IF;
-- 2. end_customers table
IF NOT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'public' AND tablename = 'end_customers') THEN
CREATE TABLE public.end_customers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
partner_id UUID NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE,
company_name TEXT NOT NULL,
vat_id TEXT,
first_name TEXT,
last_name TEXT,
street TEXT,
zip TEXT,
city TEXT,
is_anonymized BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
repaired_tables := array_append(repaired_tables, 'end_customers');
END IF;
-- Enable RLS
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.end_customers ENABLE ROW LEVEL SECURITY;
-- Recreate RLS Policies
DROP POLICY IF EXISTS "Users can view their own profile" ON public.profiles;
CREATE POLICY "Users can view their own profile" ON public.profiles FOR SELECT USING (auth.uid() = id);
DROP POLICY IF EXISTS "Users can update their own profile" ON public.profiles;
CREATE POLICY "Users can update their own profile" ON public.profiles FOR UPDATE USING (auth.uid() = id);
DROP POLICY IF EXISTS "Users can insert their own profile" ON public.profiles;
CREATE POLICY "Users can insert their own profile" ON public.profiles FOR INSERT WITH CHECK (auth.uid() = id);
result := jsonb_build_object(
'repaired_tables', repaired_tables
);
RETURN result;
END;
$$ LANGUAGE plpgsql;
-- Reload Schema Cache
NOTIFY pgrst, 'reload schema';