feat: implement admin validation middleware, DB integrity scan, automatic schema repair, and reindexing dashboard
This commit is contained in:
283
shop/app/admin/tools/page.tsx
Normal file
283
shop/app/admin/tools/page.tsx
Normal file
@@ -0,0 +1,283 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
getDatabaseIntegrity,
|
||||
repairDatabaseSchema,
|
||||
optimizeDatabaseIndices
|
||||
} from "@/lib/actions/admin";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Loader2, RefreshCw, Wrench, Database, AlertTriangle, CheckCircle2, XCircle, Terminal, ArrowLeft } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
interface TableStatus {
|
||||
exists: boolean;
|
||||
count: number;
|
||||
}
|
||||
|
||||
interface IntegrityData {
|
||||
tables: {
|
||||
profiles: TableStatus;
|
||||
end_customers: TableStatus;
|
||||
pos_modules: TableStatus;
|
||||
licenses: TableStatus;
|
||||
};
|
||||
errors: {
|
||||
foreign_keys: number;
|
||||
};
|
||||
}
|
||||
|
||||
export default function AdminToolsPage() {
|
||||
const [integrity, setIntegrity] = useState<IntegrityData | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [logs, setLogs] = useState<string[]>([]);
|
||||
const [activeAction, setActiveAction] = useState<string | null>(null);
|
||||
|
||||
const addLog = (message: string) => {
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
setLogs((prev) => [...prev, `[${timestamp}] ${message}`]);
|
||||
};
|
||||
|
||||
const loadStatus = async (quiet = false) => {
|
||||
if (!quiet) setIsLoading(true);
|
||||
addLog("Starte Integritätsscan der Datenbank...");
|
||||
const res = await getDatabaseIntegrity();
|
||||
if (res.success && res.data) {
|
||||
setIntegrity(res.data as IntegrityData);
|
||||
addLog("Integritätsscan erfolgreich abgeschlossen.");
|
||||
} else {
|
||||
addLog(`FEHLER beim Scannen: ${res.error}`);
|
||||
}
|
||||
if (!quiet) setIsLoading(false);
|
||||
};
|
||||
|
||||
const handleRepair = async () => {
|
||||
setIsLoading(true);
|
||||
setActiveAction("repair");
|
||||
addLog("Starte automatische Schemareparatur...");
|
||||
const res = await repairDatabaseSchema();
|
||||
if (res.success && res.data) {
|
||||
const repaired = (res.data as any).repaired || [];
|
||||
if (repaired.length > 0) {
|
||||
addLog(`Erfolgreich reparierte Tabellen: ${repaired.join(", ")}`);
|
||||
} else {
|
||||
addLog("Keine fehlenden Tabellen gefunden. Schema intakt.");
|
||||
}
|
||||
await loadStatus(true);
|
||||
} else {
|
||||
addLog(`FEHLER bei Schemareparatur: ${res.error}`);
|
||||
}
|
||||
setIsLoading(false);
|
||||
setActiveAction(null);
|
||||
};
|
||||
|
||||
const handleOptimize = async () => {
|
||||
setIsLoading(true);
|
||||
setActiveAction("optimize");
|
||||
addLog("Starte Reindexierung des Schemas public...");
|
||||
const res = await optimizeDatabaseIndices();
|
||||
if (res.success) {
|
||||
addLog("Reindexierung erfolgreich abgeschlossen. Suchindizes neu aufgebaut.");
|
||||
} else {
|
||||
addLog(`FEHLER bei Reindexierung: ${res.error}`);
|
||||
}
|
||||
setIsLoading(false);
|
||||
setActiveAction(null);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadStatus();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#020617] text-white px-4 py-12 relative overflow-hidden">
|
||||
{/* Background decoration */}
|
||||
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-[1000px] h-[600px] bg-blue-600/10 blur-[120px] rounded-full -z-10 opacity-30" />
|
||||
|
||||
{/* Loading Overlay */}
|
||||
{isLoading && (
|
||||
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex flex-col items-center justify-center gap-4">
|
||||
<Loader2 className="w-12 h-12 text-primary animate-spin" />
|
||||
<p className="text-slate-200 text-lg font-medium">
|
||||
{activeAction === "repair"
|
||||
? "Schema wird repariert..."
|
||||
: activeAction === "optimize"
|
||||
? "Datenbank wird indexiert..."
|
||||
: "Verbindung zur Datenbank wird aufgebaut..."}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="max-w-5xl mx-auto space-y-8">
|
||||
{/* Header */}
|
||||
<div className="flex items-center gap-4">
|
||||
<Link href="/">
|
||||
<Button variant="ghost" size="sm" className="text-slate-400 hover:text-white">
|
||||
<ArrowLeft className="w-4 h-4 mr-2" /> Startseite
|
||||
</Button>
|
||||
</Link>
|
||||
<div>
|
||||
<h1 className="text-3xl font-extrabold tracking-tight flex items-center gap-3">
|
||||
<Database className="w-8 h-8 text-primary" />
|
||||
Admin Datenbank-Cockpit
|
||||
</h1>
|
||||
<p className="text-slate-400 text-sm mt-1">
|
||||
Verwalten und reparieren Sie Systemtabellen, Sicherheitsrichtlinien (RLS) und Datenbank-Indizes.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Controls */}
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
<Card className="glass-dark border-white/10 flex flex-col justify-between">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white text-lg flex items-center gap-2">
|
||||
<RefreshCw className="w-5 h-5 text-blue-400" />
|
||||
Integritätsscan
|
||||
</CardTitle>
|
||||
<CardDescription className="text-slate-400">
|
||||
Prüft die physische Existenz der Tabellen und zählt deren Zeilenanzahl.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<Button
|
||||
onClick={() => loadStatus()}
|
||||
disabled={isLoading}
|
||||
className="w-full bg-blue-600 hover:bg-blue-700 text-white"
|
||||
>
|
||||
Status aktualisieren
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="glass-dark border-white/10 flex flex-col justify-between">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white text-lg flex items-center gap-2">
|
||||
<Wrench className="w-5 h-5 text-emerald-400" />
|
||||
Schema reparieren
|
||||
</CardTitle>
|
||||
<CardDescription className="text-slate-400">
|
||||
Erstellt fehlende Kerntabellen automatisch neu und konfiguriert die RLS-Rechte.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<Button
|
||||
onClick={handleRepair}
|
||||
disabled={isLoading}
|
||||
className="w-full bg-emerald-600 hover:bg-emerald-700 text-white"
|
||||
>
|
||||
Schema reparieren
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="glass-dark border-white/10 flex flex-col justify-between">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white text-lg flex items-center gap-2">
|
||||
<Database className="w-5 h-5 text-amber-400" />
|
||||
Index optimieren
|
||||
</CardTitle>
|
||||
<CardDescription className="text-slate-400">
|
||||
Baut korrupte Suchindizes im Schema public mittels REINDEX neu auf.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<Button
|
||||
onClick={handleOptimize}
|
||||
disabled={isLoading}
|
||||
className="w-full bg-amber-600 hover:bg-amber-700 text-white"
|
||||
>
|
||||
Index optimieren
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Tabellenstatus */}
|
||||
<Card className="glass-dark border-white/10">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white text-xl">Tabellen-Status & Diagnose</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="grid sm:grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{integrity?.tables ? (
|
||||
Object.entries(integrity.tables).map(([name, status]) => (
|
||||
<div
|
||||
key={name}
|
||||
className="p-4 rounded-xl border bg-white/5 border-white/10 flex flex-col justify-between gap-2"
|
||||
>
|
||||
<span className="font-semibold text-slate-300 text-sm capitalize">
|
||||
{name.replace("_", " ")}
|
||||
</span>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xs text-slate-500">Zeilen: {status.count}</span>
|
||||
{status.exists ? (
|
||||
<Badge className="bg-emerald-500/10 text-emerald-400 border-emerald-500/20 gap-1">
|
||||
<CheckCircle2 className="w-3.5 h-3.5" /> OK
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge className="bg-red-500/10 text-red-400 border-red-500/20 gap-1">
|
||||
<XCircle className="w-3.5 h-3.5" /> Fehlt
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="col-span-4 py-8 text-center text-slate-500 text-sm">
|
||||
Lade Diagnosedaten...
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Foreign Key Errors */}
|
||||
{integrity && integrity.errors.foreign_keys > 0 && (
|
||||
<div className="p-4 rounded-xl bg-red-500/10 border border-red-500/20 flex gap-3 items-center">
|
||||
<AlertTriangle className="w-6 h-6 text-red-400 shrink-0" />
|
||||
<div>
|
||||
<p className="font-bold text-red-200">Fremdschlüssel-Fehler gefunden!</p>
|
||||
<p className="text-red-300/80 text-sm">
|
||||
Es wurden {integrity.errors.foreign_keys} verwaiste Lizenzen ohne zugeordneten Endkunden in der Tabelle `licenses` detektiert.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Console / Output logs */}
|
||||
<Card className="glass-dark border-white/10">
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<CardTitle className="text-white text-lg flex items-center gap-2">
|
||||
<Terminal className="w-5 h-5 text-primary" />
|
||||
Diagnose-Konsole
|
||||
</CardTitle>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setLogs([])}
|
||||
className="text-slate-400 hover:text-white text-xs"
|
||||
>
|
||||
Konsole leeren
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<div className="font-mono text-xs bg-black/50 p-4 rounded-xl border border-white/5 h-64 overflow-y-auto space-y-1.5 scrollbar-thin">
|
||||
{logs.length === 0 ? (
|
||||
<span className="text-slate-600 italic">Konsole bereit. Führen Sie einen Scan aus.</span>
|
||||
) : (
|
||||
logs.map((log, index) => (
|
||||
<div key={index} className={log.includes("FEHLER") ? "text-red-400" : log.includes("Erfolgreich") ? "text-emerald-400" : "text-slate-300"}>
|
||||
{log}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
49
shop/lib/actions/admin.ts
Normal file
49
shop/lib/actions/admin.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
'use server'
|
||||
|
||||
import { createClient } from '@/lib/supabase/server'
|
||||
import { verifyAdmin } from '@/lib/actions/auth'
|
||||
|
||||
export async function getDatabaseIntegrity() {
|
||||
try {
|
||||
await verifyAdmin()
|
||||
const supabase = await createClient()
|
||||
const { data, error } = await supabase.rpc('check_database_integrity')
|
||||
if (error) {
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
return { success: true, data }
|
||||
} catch (err: any) {
|
||||
console.error("Error in getDatabaseIntegrity:", err)
|
||||
return { success: false, error: err.message || "Unerwarteter Fehler beim Integritätsscan." }
|
||||
}
|
||||
}
|
||||
|
||||
export async function repairDatabaseSchema() {
|
||||
try {
|
||||
await verifyAdmin()
|
||||
const supabase = await createClient()
|
||||
const { data, error } = await supabase.rpc('repair_database_schema')
|
||||
if (error) {
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
return { success: true, data }
|
||||
} catch (err: any) {
|
||||
console.error("Error in repairDatabaseSchema:", err)
|
||||
return { success: false, error: err.message || "Unerwarteter Fehler bei der Schemareparatur." }
|
||||
}
|
||||
}
|
||||
|
||||
export async function optimizeDatabaseIndices() {
|
||||
try {
|
||||
await verifyAdmin()
|
||||
const supabase = await createClient()
|
||||
const { data, error } = await supabase.rpc('optimize_database_indices')
|
||||
if (error) {
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
return { success: true, data }
|
||||
} catch (err: any) {
|
||||
console.error("Error in optimizeDatabaseIndices:", err)
|
||||
return { success: false, error: err.message || "Unerwarteter Fehler bei der Indexoptimierung." }
|
||||
}
|
||||
}
|
||||
@@ -98,3 +98,20 @@ export async function updatePassword(password: string) {
|
||||
}
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
export async function verifyAdmin() {
|
||||
const supabase = await createClient()
|
||||
const { data: { user } } = await supabase.auth.getUser()
|
||||
if (!user) throw new Error("Nicht authentifiziert.")
|
||||
|
||||
const { data: userData, error } = await supabase
|
||||
.from('users')
|
||||
.select('role')
|
||||
.eq('id', user.id)
|
||||
.single()
|
||||
|
||||
if (error || !userData || userData.role !== 'admin') {
|
||||
throw new Error("Zugriff verweigert. Nur Administratoren erlaubt.")
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
@@ -63,6 +63,27 @@ export async function updateSession(request: NextRequest) {
|
||||
return NextResponse.redirect(url);
|
||||
}
|
||||
|
||||
// Admin protection
|
||||
if (request.nextUrl.pathname.startsWith("/admin")) {
|
||||
if (!user || !user.sub) {
|
||||
const url = request.nextUrl.clone();
|
||||
url.pathname = "/auth/login";
|
||||
return NextResponse.redirect(url);
|
||||
}
|
||||
|
||||
const { data: dbUser } = await supabase
|
||||
.from("users")
|
||||
.select("role")
|
||||
.eq("id", user.sub)
|
||||
.single();
|
||||
|
||||
if (!dbUser || dbUser.role !== "admin") {
|
||||
const url = request.nextUrl.clone();
|
||||
url.pathname = "/";
|
||||
return NextResponse.redirect(url);
|
||||
}
|
||||
}
|
||||
|
||||
// IMPORTANT: You *must* return the supabaseResponse object as it is.
|
||||
// If you're creating a new response object with NextResponse.next() make sure to:
|
||||
// 1. Pass the request in it, like so:
|
||||
|
||||
195
shop/supabase/migrations/20260624000000_integrity_functions.sql
Normal file
195
shop/supabase/migrations/20260624000000_integrity_functions.sql
Normal file
@@ -0,0 +1,195 @@
|
||||
-- CREATE FUNCTION public.check_database_integrity
|
||||
CREATE OR REPLACE FUNCTION public.check_database_integrity()
|
||||
RETURNS jsonb
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
DECLARE
|
||||
profiles_exist boolean;
|
||||
end_customers_exist boolean;
|
||||
pos_modules_exist boolean;
|
||||
licenses_exist boolean;
|
||||
|
||||
profiles_count bigint := 0;
|
||||
end_customers_count bigint := 0;
|
||||
pos_modules_count bigint := 0;
|
||||
licenses_count bigint := 0;
|
||||
|
||||
fk_errors_count bigint := 0;
|
||||
result jsonb;
|
||||
BEGIN
|
||||
-- Check table existence
|
||||
SELECT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'public' AND tablename = 'profiles') INTO profiles_exist;
|
||||
SELECT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'public' AND tablename = 'end_customers') INTO end_customers_exist;
|
||||
SELECT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'public' AND tablename = 'pos_modules') INTO pos_modules_exist;
|
||||
SELECT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'public' AND tablename = 'licenses') INTO licenses_exist;
|
||||
|
||||
-- Count rows if table exists
|
||||
IF profiles_exist THEN
|
||||
EXECUTE 'SELECT count(*) FROM public.profiles' INTO profiles_count;
|
||||
END IF;
|
||||
|
||||
IF end_customers_exist THEN
|
||||
EXECUTE 'SELECT count(*) FROM public.end_customers' INTO end_customers_count;
|
||||
END IF;
|
||||
|
||||
IF pos_modules_exist THEN
|
||||
EXECUTE 'SELECT count(*) FROM public.pos_modules' INTO pos_modules_count;
|
||||
END IF;
|
||||
|
||||
IF licenses_exist THEN
|
||||
EXECUTE 'SELECT count(*) FROM public.licenses' INTO licenses_count;
|
||||
END IF;
|
||||
|
||||
-- Count foreign key errors (orphaned licenses)
|
||||
IF licenses_exist AND end_customers_exist THEN
|
||||
EXECUTE '
|
||||
SELECT count(*)
|
||||
FROM public.licenses l
|
||||
LEFT JOIN public.end_customers c ON l.end_customer_id = c.id
|
||||
WHERE c.id IS NULL AND l.end_customer_id IS NOT NULL
|
||||
' INTO fk_errors_count;
|
||||
END IF;
|
||||
|
||||
-- Build JSON result
|
||||
result := jsonb_build_object(
|
||||
'tables', jsonb_build_object(
|
||||
'profiles', jsonb_build_object('exists', profiles_exist, 'count', profiles_count),
|
||||
'end_customers', jsonb_build_object('exists', end_customers_exist, 'count', end_customers_count),
|
||||
'pos_modules', jsonb_build_object('exists', pos_modules_exist, 'count', pos_modules_count),
|
||||
'licenses', jsonb_build_object('exists', licenses_exist, 'count', licenses_count)
|
||||
),
|
||||
'errors', jsonb_build_object(
|
||||
'foreign_keys', fk_errors_count
|
||||
)
|
||||
);
|
||||
|
||||
RETURN result;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- CREATE FUNCTION public.repair_database_schema
|
||||
CREATE OR REPLACE FUNCTION public.repair_database_schema()
|
||||
RETURNS jsonb
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
DECLARE
|
||||
repaired_tables text[] := '{}';
|
||||
result jsonb;
|
||||
BEGIN
|
||||
-- 1. profiles table (should exist, but check just in case)
|
||||
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.profiles(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;
|
||||
|
||||
-- 3. pos_modules table
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'public' AND tablename = 'pos_modules') THEN
|
||||
CREATE TABLE public.pos_modules (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL,
|
||||
price NUMERIC(10, 2) NOT NULL DEFAULT 0.00,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
repaired_tables := array_append(repaired_tables, 'pos_modules');
|
||||
END IF;
|
||||
|
||||
-- 4. licenses table
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'public' AND tablename = 'licenses') THEN
|
||||
CREATE TABLE public.licenses (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
end_customer_id UUID REFERENCES public.end_customers(id) ON DELETE CASCADE,
|
||||
pos_module_id UUID REFERENCES public.pos_modules(id) ON DELETE CASCADE,
|
||||
license_key TEXT NOT NULL UNIQUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
repaired_tables := array_append(repaired_tables, 'licenses');
|
||||
END IF;
|
||||
|
||||
-- Enable RLS on all tables
|
||||
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.end_customers ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.pos_modules ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.licenses ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Recreate RLS Policies
|
||||
|
||||
-- profiles 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);
|
||||
DROP POLICY IF EXISTS "Admins can view all profiles" ON public.profiles;
|
||||
CREATE POLICY "Admins can view all profiles" ON public.profiles FOR SELECT USING (auth.role() = 'authenticated');
|
||||
|
||||
-- end_customers Policies
|
||||
DROP POLICY IF EXISTS "Partner sehen nur eigene Endkunden" ON public.end_customers;
|
||||
CREATE POLICY "Partner sehen nur eigene Endkunden" ON public.end_customers FOR ALL USING (auth.uid() = partner_id) WITH CHECK (auth.uid() = partner_id);
|
||||
|
||||
-- pos_modules Policies
|
||||
DROP POLICY IF EXISTS "Allow authenticated read on pos_modules" ON public.pos_modules;
|
||||
CREATE POLICY "Allow authenticated read on pos_modules" ON public.pos_modules FOR SELECT USING (auth.role() = 'authenticated');
|
||||
|
||||
-- licenses Policies
|
||||
DROP POLICY IF EXISTS "Partner sehen Lizenzen eigener Kunden" ON public.licenses;
|
||||
CREATE POLICY "Partner sehen Lizenzen eigener Kunden" ON public.licenses FOR SELECT USING (
|
||||
EXISTS (
|
||||
SELECT 1 FROM public.end_customers c
|
||||
WHERE c.id = licenses.end_customer_id AND c.partner_id = auth.uid()
|
||||
)
|
||||
);
|
||||
DROP POLICY IF EXISTS "Partner erstellen Lizenzen eigener Kunden" ON public.licenses;
|
||||
CREATE POLICY "Partner erstellen Lizenzen eigener Kunden" ON public.licenses FOR INSERT WITH CHECK (
|
||||
EXISTS (
|
||||
SELECT 1 FROM public.end_customers c
|
||||
WHERE c.id = licenses.end_customer_id AND c.partner_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
result := jsonb_build_object(
|
||||
'repaired', to_jsonb(repaired_tables),
|
||||
'success', true
|
||||
);
|
||||
RETURN result;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- CREATE FUNCTION public.optimize_database_indices
|
||||
CREATE OR REPLACE FUNCTION public.optimize_database_indices()
|
||||
RETURNS jsonb
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
BEGIN
|
||||
REINDEX SCHEMA public;
|
||||
RETURN jsonb_build_object('success', true, 'message', 'Schema public successfully reindexed.');
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
Reference in New Issue
Block a user