Initial commit from template

This commit is contained in:
Lumina
2025-12-23 04:19:57 +01:00
commit b3d8fe8dfe
76 changed files with 10491 additions and 0 deletions

286
.claude/commands/api.md Normal file
View File

@@ -0,0 +1,286 @@
# API Route Generator
Du bist ein Experte für Next.js API Routes. Erstelle sichere, typisierte API Endpoints.
## Deine Aufgaben
Erstelle API Routes die:
- Next.js App Router Patterns folgen
- TypeScript mit korrekten Types verwenden
- Error Handling implementieren
- Input Validation durchführen
- Supabase Integration nutzen
## API Route Template
```typescript
// app/api/[resource]/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
// GET - Liste oder einzelnes Item
export async function GET(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const id = searchParams.get('id');
if (id) {
// Single item
const { data, error } = await supabase
.from('table')
.select('*')
.eq('id', id)
.single();
if (error) throw error;
return NextResponse.json(data);
}
// List
const { data, error } = await supabase
.from('table')
.select('*')
.order('created_at', { ascending: false });
if (error) throw error;
return NextResponse.json(data);
} catch (error) {
console.error('GET error:', error);
return NextResponse.json(
{ error: 'Failed to fetch data' },
{ status: 500 }
);
}
}
// POST - Neues Item erstellen
export async function POST(request: NextRequest) {
try {
const body = await request.json();
// Validation
if (!body.name) {
return NextResponse.json(
{ error: 'Name is required' },
{ status: 400 }
);
}
const { data, error } = await supabase
.from('table')
.insert(body)
.select()
.single();
if (error) throw error;
return NextResponse.json(data, { status: 201 });
} catch (error) {
console.error('POST error:', error);
return NextResponse.json(
{ error: 'Failed to create item' },
{ status: 500 }
);
}
}
// PUT - Item aktualisieren
export async function PUT(request: NextRequest) {
try {
const body = await request.json();
const { id, ...updates } = body;
if (!id) {
return NextResponse.json(
{ error: 'ID is required' },
{ status: 400 }
);
}
const { data, error } = await supabase
.from('table')
.update(updates)
.eq('id', id)
.select()
.single();
if (error) throw error;
return NextResponse.json(data);
} catch (error) {
console.error('PUT error:', error);
return NextResponse.json(
{ error: 'Failed to update item' },
{ status: 500 }
);
}
}
// DELETE - Item löschen
export async function DELETE(request: NextRequest) {
try {
const { searchParams } = new URL(request.url);
const id = searchParams.get('id');
if (!id) {
return NextResponse.json(
{ error: 'ID is required' },
{ status: 400 }
);
}
const { error } = await supabase
.from('table')
.delete()
.eq('id', id);
if (error) throw error;
return NextResponse.json({ success: true });
} catch (error) {
console.error('DELETE error:', error);
return NextResponse.json(
{ error: 'Failed to delete item' },
{ status: 500 }
);
}
}
```
## Dynamic Route
```typescript
// app/api/users/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server';
interface RouteParams {
params: Promise<{ id: string }>;
}
export async function GET(request: NextRequest, { params }: RouteParams) {
const { id } = await params;
const { data, error } = await supabase
.from('users')
.select('*')
.eq('id', id)
.single();
if (error || !data) {
return NextResponse.json({ error: 'User not found' }, { status: 404 });
}
return NextResponse.json(data);
}
```
## Auth Protected Route
```typescript
// app/api/protected/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { createServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers';
export async function GET(request: NextRequest) {
const cookieStore = await cookies();
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return cookieStore.get(name)?.value;
},
},
}
);
const { data: { user }, error } = await supabase.auth.getUser();
if (error || !user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// User is authenticated
return NextResponse.json({ user });
}
```
## Input Validation mit Zod
```typescript
import { z } from 'zod';
const createUserSchema = z.object({
email: z.string().email(),
name: z.string().min(2).max(100),
role: z.enum(['user', 'admin']).optional().default('user'),
});
export async function POST(request: NextRequest) {
try {
const body = await request.json();
// Validate input
const result = createUserSchema.safeParse(body);
if (!result.success) {
return NextResponse.json(
{ error: 'Validation failed', details: result.error.flatten() },
{ status: 400 }
);
}
const validatedData = result.data;
// ... create user
} catch (error) {
return NextResponse.json({ error: 'Server error' }, { status: 500 });
}
}
```
## Response Helpers
```typescript
// lib/api-response.ts
import { NextResponse } from 'next/server';
export function successResponse<T>(data: T, status = 200) {
return NextResponse.json({ success: true, data }, { status });
}
export function errorResponse(message: string, status = 500) {
return NextResponse.json({ success: false, error: message }, { status });
}
export function notFoundResponse(resource = 'Resource') {
return errorResponse(`${resource} not found`, 404);
}
export function unauthorizedResponse() {
return errorResponse('Unauthorized', 401);
}
export function validationErrorResponse(errors: unknown) {
return NextResponse.json(
{ success: false, error: 'Validation failed', details: errors },
{ status: 400 }
);
}
```
## Best Practices
1. **Error Handling** - Immer try/catch verwenden
2. **Input Validation** - Alle Inputs validieren (Zod empfohlen)
3. **Auth Check** - Geschützte Routen absichern
4. **Status Codes** - Korrekte HTTP Status Codes
5. **Logging** - Errors loggen für Debugging
---
Frage den Benutzer: Welche API Route möchtest du erstellen?
Beschreibe die gewünschte Ressource und Operationen.

View File

@@ -0,0 +1,249 @@
# UI Component Generator
Du bist ein Experte für React/Next.js Components mit Tailwind CSS und Spartan UI. Erstelle moderne, wiederverwendbare UI Components.
## Deine Aufgaben
Erstelle Components die:
- TypeScript mit korrekten Props-Typen verwenden
- Tailwind CSS für Styling nutzen
- Spartan UI Patterns folgen
- Accessible sind (ARIA, Keyboard Navigation)
- Server/Client Component korrekt trennen
## Component Template
```typescript
// components/ui/[ComponentName].tsx
import { cn } from '@/lib/utils';
interface ComponentNameProps {
children?: React.ReactNode;
className?: string;
variant?: 'default' | 'primary' | 'secondary';
size?: 'sm' | 'md' | 'lg';
}
export function ComponentName({
children,
className,
variant = 'default',
size = 'md',
}: ComponentNameProps) {
return (
<div
className={cn(
// Base styles
'rounded-lg border',
// Variants
{
'bg-background': variant === 'default',
'bg-primary text-primary-foreground': variant === 'primary',
'bg-secondary text-secondary-foreground': variant === 'secondary',
},
// Sizes
{
'p-2 text-sm': size === 'sm',
'p-4 text-base': size === 'md',
'p-6 text-lg': size === 'lg',
},
className
)}
>
{children}
</div>
);
}
```
## Häufige Components
### Button
```typescript
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'default' | 'primary' | 'outline' | 'ghost' | 'destructive';
size?: 'sm' | 'md' | 'lg' | 'icon';
loading?: boolean;
}
export function Button({
children,
className,
variant = 'default',
size = 'md',
loading = false,
disabled,
...props
}: ButtonProps) {
return (
<button
className={cn(
'inline-flex items-center justify-center rounded-md font-medium transition-colors',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
'disabled:pointer-events-none disabled:opacity-50',
{
'bg-primary text-primary-foreground hover:bg-primary/90': variant === 'primary',
'bg-secondary text-secondary-foreground hover:bg-secondary/80': variant === 'default',
'border border-input hover:bg-accent': variant === 'outline',
'hover:bg-accent': variant === 'ghost',
'bg-destructive text-destructive-foreground hover:bg-destructive/90': variant === 'destructive',
},
{
'h-8 px-3 text-sm': size === 'sm',
'h-10 px-4': size === 'md',
'h-12 px-6 text-lg': size === 'lg',
'h-10 w-10': size === 'icon',
},
className
)}
disabled={disabled || loading}
{...props}
>
{loading && <Spinner className="mr-2 h-4 w-4" />}
{children}
</button>
);
}
```
### Card
```typescript
export function Card({ children, className }: { children: React.ReactNode; className?: string }) {
return (
<div className={cn('rounded-xl border bg-card p-6 shadow-sm', className)}>
{children}
</div>
);
}
export function CardHeader({ children, className }: { children: React.ReactNode; className?: string }) {
return <div className={cn('mb-4', className)}>{children}</div>;
}
export function CardTitle({ children, className }: { children: React.ReactNode; className?: string }) {
return <h3 className={cn('text-xl font-semibold', className)}>{children}</h3>;
}
export function CardContent({ children, className }: { children: React.ReactNode; className?: string }) {
return <div className={cn('', className)}>{children}</div>;
}
```
### Input
```typescript
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label?: string;
error?: string;
}
export function Input({ label, error, className, id, ...props }: InputProps) {
const inputId = id || label?.toLowerCase().replace(/\s/g, '-');
return (
<div className="space-y-1">
{label && (
<label htmlFor={inputId} className="text-sm font-medium">
{label}
</label>
)}
<input
id={inputId}
className={cn(
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2',
'text-sm placeholder:text-muted-foreground',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
'disabled:cursor-not-allowed disabled:opacity-50',
error && 'border-destructive',
className
)}
{...props}
/>
{error && <p className="text-sm text-destructive">{error}</p>}
</div>
);
}
```
### Modal/Dialog
```typescript
'use client';
import { useEffect } from 'react';
import { cn } from '@/lib/utils';
interface ModalProps {
open: boolean;
onClose: () => void;
children: React.ReactNode;
className?: string;
}
export function Modal({ open, onClose, children, className }: ModalProps) {
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
};
if (open) {
document.addEventListener('keydown', handleEscape);
document.body.style.overflow = 'hidden';
}
return () => {
document.removeEventListener('keydown', handleEscape);
document.body.style.overflow = '';
};
}, [open, onClose]);
if (!open) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
<div className="fixed inset-0 bg-black/50" onClick={onClose} />
<div
className={cn(
'relative z-50 w-full max-w-lg rounded-lg bg-background p-6 shadow-lg',
className
)}
>
{children}
</div>
</div>
);
}
```
## Client vs Server Components
```typescript
// Server Component (default) - keine Interaktivität
// components/UserList.tsx
export async function UserList() {
const users = await fetchUsers();
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
// Client Component - mit Interaktivität
// components/Counter.tsx
'use client';
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
```
## Best Practices
1. **cn() Helper** - Für conditional classes
2. **Forwardref** - Für DOM-Zugriff von außen
3. **Composition** - Kleine, kombinierbare Components
4. **Accessibility** - ARIA Labels, Keyboard Support
5. **Dark Mode** - CSS Variables für Theming
---
Frage den Benutzer: Welche Component möchtest du erstellen?
Beschreibe die gewünschte Funktionalität und das Design.

View File

@@ -0,0 +1,157 @@
# PostgreSQL Datenbank Verbindung
Du verbindest dich mit der PostgreSQL Datenbank des Projekts.
## Automatischer Setup
### 1. Betriebssystem erkennen
Prüfe zuerst das Betriebssystem:
```bash
uname -s
```
### 2. PostgreSQL CLI installieren (falls nicht vorhanden)
**macOS:**
```bash
# Prüfen ob psql installiert ist
which psql || brew install postgresql
```
**Linux (Ubuntu/Debian):**
```bash
which psql || sudo apt-get update && sudo apt-get install -y postgresql-client
```
**Linux (Alpine):**
```bash
which psql || apk add postgresql-client
```
### 3. Environment Variable lesen
Lese die `DATABASE_URL` aus `.env.local` oder `.env`:
```bash
# Aus .env.local
grep DATABASE_URL .env.local 2>/dev/null || grep DATABASE_URL .env 2>/dev/null
```
Die URL hat das Format:
```
postgresql://USER:PASSWORD@HOST:PORT/DATABASE
```
### 4. Mit Datenbank verbinden
```bash
# Direkte Verbindung mit URL
psql "$DATABASE_URL"
# Oder mit Supabase
psql "postgresql://postgres.[PROJECT-REF]:[PASSWORD]@aws-0-eu-central-1.pooler.supabase.com:6543/postgres"
```
## Häufige Befehle nach Verbindung
```sql
-- Alle Tabellen anzeigen
\dt
-- Tabellen mit Schema
\dt public.*
-- Tabellen-Details
\d table_name
-- Alle Schemas
\dn
-- Benutzer anzeigen
\du
-- Aktuelle Datenbank
SELECT current_database();
-- Tabellen-Größen
SELECT
tablename,
pg_size_pretty(pg_total_relation_size(schemaname || '.' || tablename)) as size
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname || '.' || tablename) DESC;
```
## Supabase spezifische Queries
```sql
-- Auth Users (Supabase)
SELECT id, email, created_at FROM auth.users LIMIT 10;
-- Storage Buckets
SELECT * FROM storage.buckets;
-- RLS Policies
SELECT * FROM pg_policies WHERE schemaname = 'public';
-- Enabled Extensions
SELECT * FROM pg_extension;
```
## SQL Datei ausführen
```bash
# SQL Datei ausführen
psql "$DATABASE_URL" -f migrations/001_init.sql
# Mit Output
psql "$DATABASE_URL" -f migrations/001_init.sql -v ON_ERROR_STOP=1
```
## Backup & Restore
```bash
# Backup erstellen
pg_dump "$DATABASE_URL" > backup.sql
# Nur Schema
pg_dump "$DATABASE_URL" --schema-only > schema.sql
# Nur Daten
pg_dump "$DATABASE_URL" --data-only > data.sql
# Restore
psql "$DATABASE_URL" < backup.sql
```
## Troubleshooting
### Connection refused
- Supabase Projekt ist pausiert → Dashboard öffnen
- Firewall blockiert Port 5432/6543
- IP nicht in Allowlist (Supabase: Database Settings > Connection Pooling)
### SSL required
```bash
psql "$DATABASE_URL?sslmode=require"
```
### Password authentication failed
- Passwort in URL URL-encoded? (`@``%40`, etc.)
- Richtiges Passwort aus Supabase Dashboard
---
## Automatische Verbindung
Führe diese Schritte aus:
1. **OS prüfen:** `uname -s`
2. **psql prüfen:** `which psql`
3. **Falls nicht vorhanden:** Installiere je nach OS
4. **DATABASE_URL laden:** Aus .env.local oder .env
5. **Verbinden:** `psql "$DATABASE_URL"`
Soll ich die Verbindung jetzt herstellen?

220
.claude/commands/deploy.md Normal file
View File

@@ -0,0 +1,220 @@
# Deployment Assistent
Du bist ein Experte für das Deployment von Next.js Anwendungen mit Docker und Kubernetes.
## Deployment Optionen
### 1. Lokales Development
```bash
# Dependencies installieren
pnpm install
# Environment Variables
cp .env.example .env.local
# .env.local bearbeiten
# Development Server starten
pnpm dev
```
### 2. Docker Deployment
#### Docker Image bauen
```bash
# Production Image bauen
docker build -t lumina-app:latest .
# Mit spezifischem Tag
docker build -t lumina-app:v1.0.0 .
# Lokaler Test
docker run -p 3000:3000 --env-file .env.local lumina-app:latest
```
#### Docker Compose (empfohlen für lokales Testing)
```bash
# Alle Services starten
docker-compose up -d
# Logs ansehen
docker-compose logs -f app
# Services stoppen
docker-compose down
# Mit Volume Cleanup
docker-compose down -v
```
### 3. Harbor Registry
```bash
# Bei Harbor einloggen
docker login harbor.advisori.de
# Image taggen
docker tag lumina-app:latest harbor.advisori.de/lumina/PROJECT_ID:latest
docker tag lumina-app:latest harbor.advisori.de/lumina/PROJECT_ID:v1.0.0
# Pushen
docker push harbor.advisori.de/lumina/PROJECT_ID:latest
docker push harbor.advisori.de/lumina/PROJECT_ID:v1.0.0
```
### 4. Kubernetes Deployment
#### Voraussetzungen
```bash
# Namespace erstellen
kubectl create namespace lumina-apps
# Harbor Pull Secret
kubectl create secret docker-registry harbor-registry-secret \
--docker-server=harbor.advisori.de \
--docker-username=YOUR_USERNAME \
--docker-password=YOUR_PASSWORD \
--namespace=lumina-apps
# Application Secrets
kubectl create secret generic app-secrets \
--from-literal=DATABASE_URL="postgresql://..." \
--from-literal=SUPABASE_URL="https://..." \
--from-literal=SUPABASE_ANON_KEY="..." \
--from-literal=SUPABASE_SERVICE_ROLE_KEY="..." \
--namespace=lumina-apps
```
#### Helm Deployment
```bash
# In das Helm Chart Verzeichnis wechseln
cd helm/lumina-app
# Dry-run zum Testen
helm upgrade --install app . \
--namespace lumina-apps \
--set image.repository=harbor.advisori.de/lumina/PROJECT_ID \
--set image.tag=latest \
--dry-run
# Tatsächliches Deployment
helm upgrade --install app . \
--namespace lumina-apps \
--set image.repository=harbor.advisori.de/lumina/PROJECT_ID \
--set image.tag=v1.0.0 \
--set ingress.hosts[0].host=app.advisori.de \
--set ingress.tls[0].hosts[0]=app.advisori.de
# Status prüfen
helm status app -n lumina-apps
```
#### Kubectl Management
```bash
# Pods anzeigen
kubectl get pods -n lumina-apps
# Logs ansehen
kubectl logs -f deployment/app -n lumina-apps
# In Pod exec
kubectl exec -it deployment/app -n lumina-apps -- /bin/sh
# Restart Deployment
kubectl rollout restart deployment/app -n lumina-apps
# Rollback
helm rollback app -n lumina-apps
```
### 5. CI/CD Pipeline (Gitea Actions)
```yaml
# .gitea/workflows/deploy.yml
name: Build and Deploy
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Login to Harbor
uses: docker/login-action@v3
with:
registry: harbor.advisori.de
username: ${{ secrets.HARBOR_USERNAME }}
password: ${{ secrets.HARBOR_PASSWORD }}
- name: Build and Push
uses: docker/build-push-action@v5
with:
push: true
tags: |
harbor.advisori.de/lumina/${{ github.event.repository.name }}:latest
harbor.advisori.de/lumina/${{ github.event.repository.name }}:${{ github.sha }}
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Deploy to K8s
uses: azure/k8s-deploy@v4
with:
namespace: lumina-apps
manifests: helm/lumina-app
images: harbor.advisori.de/lumina/${{ github.event.repository.name }}:${{ github.sha }}
```
## Troubleshooting
### Container startet nicht
```bash
# Pod Status
kubectl describe pod POD_NAME -n lumina-apps
# Container Logs
kubectl logs POD_NAME -n lumina-apps --previous
```
### Ingress funktioniert nicht
```bash
# Ingress Status
kubectl get ingress -n lumina-apps
# Ingress Details
kubectl describe ingress app -n lumina-apps
```
### Health Check Fehler
```bash
# Health Endpoint testen
kubectl port-forward svc/app 3000:80 -n lumina-apps
curl http://localhost:3000/api/health
```
## Environment Variables Checkliste
| Variable | Beschreibung | Erforderlich |
|----------|--------------|--------------|
| `NODE_ENV` | production | Ja |
| `NEXT_PUBLIC_SUPABASE_URL` | Supabase URL | Ja |
| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Anon Key | Ja |
| `SUPABASE_SERVICE_ROLE_KEY` | Service Key | Ja |
| `DATABASE_URL` | PostgreSQL URL | Optional |
---
Frage den Benutzer: Wie möchtest du deployen?
- Lokal mit Docker Compose
- Auf Kubernetes Cluster
- CI/CD Pipeline einrichten

176
.claude/commands/setup.md Normal file
View File

@@ -0,0 +1,176 @@
# Projekt Setup Assistent
Du hilfst beim initialen Setup eines neuen Lumina-Projekts.
## Setup Checkliste
### 1. Dependencies installieren
```bash
# Mit pnpm (empfohlen)
pnpm install
# Oder mit npm
npm install
```
### 2. Environment Variables konfigurieren
```bash
# Beispiel-Datei kopieren
cp .env.example .env.local
```
Bearbeite `.env.local`:
```env
# Supabase (erforderlich)
NEXT_PUBLIC_SUPABASE_URL=https://YOUR_PROJECT.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
# Optional
DATABASE_URL=postgresql://user:pass@host:5432/db
REDIS_URL=redis://localhost:6379
```
### 3. Supabase Projekt erstellen
1. Gehe zu [supabase.com](https://supabase.com)
2. Erstelle ein neues Projekt
3. Kopiere URL und Keys aus Project Settings > API
4. Füge sie in `.env.local` ein
### 4. Datenbank initialisieren
Falls du SQL Migrationen hast:
```bash
# Via Supabase CLI
npx supabase db push
# Oder via Dashboard
# - Gehe zu SQL Editor
# - Führe deine Migrations aus
```
### 5. Development Server starten
```bash
pnpm dev
```
Öffne [http://localhost:3000](http://localhost:3000)
## Projekt Struktur anlegen
### Empfohlene Ordner
```bash
# UI Components
mkdir -p components/ui
mkdir -p components/forms
mkdir -p components/layout
# Features
mkdir -p components/features
# API Helpers
mkdir -p lib/api
mkdir -p lib/hooks
# Types
mkdir -p types
```
### Basis-Files erstellen
```typescript
// types/index.ts
export interface User {
id: string;
email: string;
name?: string;
avatar_url?: string;
created_at: string;
}
// lib/hooks/useUser.ts
'use client';
import { useAuth } from '@/contexts/AuthContext';
export function useUser() {
const { user, loading } = useAuth();
return { user, loading };
}
```
## TypeScript Types aus Supabase generieren
```bash
# Supabase CLI installieren
pnpm add -D supabase
# Login
npx supabase login
# Types generieren
npx supabase gen types typescript --project-id YOUR_PROJECT_ID > lib/database.types.ts
```
Verwendung:
```typescript
import { Database } from '@/lib/database.types';
type User = Database['public']['Tables']['users']['Row'];
type NewUser = Database['public']['Tables']['users']['Insert'];
```
## Git Setup
```bash
# Falls noch nicht initialisiert
git init
# Initial Commit
git add .
git commit -m "Initial setup"
# Remote hinzufügen (Gitea)
git remote add origin https://gitea.example.com/org/repo.git
git push -u origin main
```
## Nächste Schritte
Nach dem Setup kannst du:
1. **Auth einrichten** - `/supabase-auth`
2. **Datenbank Schema erstellen** - `/supabase-db`
3. **UI Components bauen** - `/component`
4. **API Routes erstellen** - `/api`
## Häufige Probleme
### "Module not found"
```bash
# Cache löschen
rm -rf .next node_modules
pnpm install
```
### Supabase Connection Error
- URL und Keys prüfen
- Supabase Projekt ist aktiv (nicht pausiert)
- Anon Key hat Lese-Rechte
### TypeScript Errors
```bash
# Type Check
pnpm run lint
npx tsc --noEmit
```
---
Frage den Benutzer: Was möchtest du als nächstes einrichten?

View File

@@ -0,0 +1,287 @@
# Supabase Authentication Assistent
Du bist ein Experte für Supabase Authentication. Hilf dem Benutzer bei Auth-Setup, User Management und Session Handling.
## Deine Aufgaben
### 1. Auth Setup
```typescript
// lib/supabase-auth.ts
import { createClient } from '@supabase/supabase-js';
// Browser Client
export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
// Server Client (für API Routes)
export const supabaseAdmin = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!,
{
auth: {
autoRefreshToken: false,
persistSession: false
}
}
);
```
### 2. Sign Up / Sign In
```typescript
// Email/Password Sign Up
const { data, error } = await supabase.auth.signUp({
email: 'user@example.com',
password: 'secure-password',
options: {
data: {
full_name: 'John Doe',
}
}
});
// Email/Password Sign In
const { data, error } = await supabase.auth.signInWithPassword({
email: 'user@example.com',
password: 'password'
});
// Magic Link
const { error } = await supabase.auth.signInWithOtp({
email: 'user@example.com',
options: {
emailRedirectTo: `${window.location.origin}/auth/callback`
}
});
// OAuth (Google, GitHub, etc.)
const { error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: `${window.location.origin}/auth/callback`
}
});
```
### 3. Session Management
```typescript
// Get current session
const { data: { session } } = await supabase.auth.getSession();
// Get current user
const { data: { user } } = await supabase.auth.getUser();
// Listen to auth changes
supabase.auth.onAuthStateChange((event, session) => {
if (event === 'SIGNED_IN') {
console.log('User signed in:', session?.user);
} else if (event === 'SIGNED_OUT') {
console.log('User signed out');
}
});
// Sign out
await supabase.auth.signOut();
```
### 4. Auth Context Provider
```typescript
// contexts/AuthContext.tsx
'use client';
import { createContext, useContext, useEffect, useState } from 'react';
import { User, Session } from '@supabase/supabase-js';
import { supabase } from '@/lib/supabase';
interface AuthContextType {
user: User | null;
session: Session | null;
loading: boolean;
signOut: () => Promise<void>;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [session, setSession] = useState<Session | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Get initial session
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session);
setUser(session?.user ?? null);
setLoading(false);
});
// Listen for changes
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(_event, session) => {
setSession(session);
setUser(session?.user ?? null);
}
);
return () => subscription.unsubscribe();
}, []);
const signOut = async () => {
await supabase.auth.signOut();
};
return (
<AuthContext.Provider value={{ user, session, loading, signOut }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
}
```
### 5. Protected Routes (Middleware)
```typescript
// middleware.ts
import { createServerClient, type CookieOptions } from '@supabase/ssr';
import { NextResponse, type NextRequest } from 'next/server';
export async function middleware(request: NextRequest) {
let response = NextResponse.next({
request: {
headers: request.headers,
},
});
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return request.cookies.get(name)?.value;
},
set(name: string, value: string, options: CookieOptions) {
response.cookies.set({ name, value, ...options });
},
remove(name: string, options: CookieOptions) {
response.cookies.set({ name, value: '', ...options });
},
},
}
);
const { data: { session } } = await supabase.auth.getSession();
// Protect routes
if (!session && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url));
}
return response;
}
export const config = {
matcher: ['/dashboard/:path*', '/api/protected/:path*']
};
```
### 6. Password Reset
```typescript
// Request password reset
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${window.location.origin}/auth/reset-password`
});
// Update password (after clicking reset link)
const { error } = await supabase.auth.updateUser({
password: 'new-password'
});
```
## Auth UI Components
```typescript
// components/auth/LoginForm.tsx
'use client';
import { useState } from 'react';
import { supabase } from '@/lib/supabase';
export function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError(null);
const { error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) {
setError(error.message);
}
setLoading(false);
};
return (
<form onSubmit={handleLogin}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
required
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
required
/>
{error && <p className="text-red-500">{error}</p>}
<button type="submit" disabled={loading}>
{loading ? 'Loading...' : 'Sign In'}
</button>
</form>
);
}
```
## Best Practices
1. **SSR Auth** - Nutze @supabase/ssr für Server-Side Auth
2. **Middleware** - Schütze Routen serverseitig
3. **Session Refresh** - Automatisch via Supabase Client
4. **Error Handling** - User-freundliche Fehlermeldungen
5. **Secure Cookies** - HttpOnly, Secure, SameSite
---
Frage den Benutzer: Was möchtest du mit Supabase Auth machen?
- Auth Setup implementieren
- Login/Signup Forms erstellen
- OAuth Provider einrichten
- Protected Routes konfigurieren
- User Profile Management

View File

@@ -0,0 +1,136 @@
# Supabase PostgreSQL Datenbank Assistent
Du bist ein Experte für Supabase PostgreSQL Datenbank-Operationen. Hilf dem Benutzer bei:
## Deine Aufgaben
### 1. Tabellen erstellen
Erstelle SQL Migrations mit:
- Primärschlüssel (id als UUID mit gen_random_uuid())
- created_at und updated_at Timestamps
- Row Level Security (RLS) Policies
- Indexes für häufig abgefragte Spalten
### 2. CRUD Operationen
Generiere TypeScript Code für:
```typescript
// SELECT
const { data, error } = await supabase
.from('table')
.select('*')
.eq('column', value);
// INSERT
const { data, error } = await supabase
.from('table')
.insert({ column: value })
.select();
// UPDATE
const { data, error } = await supabase
.from('table')
.update({ column: value })
.eq('id', id)
.select();
// DELETE
const { error } = await supabase
.from('table')
.delete()
.eq('id', id);
```
### 3. Komplexe Queries
- JOINs mit foreign key relationships
- Aggregationen (count, sum, avg)
- Filtering und Sorting
- Pagination mit range()
### 4. RLS Policies
```sql
-- Enable RLS
ALTER TABLE table_name ENABLE ROW LEVEL SECURITY;
-- Policy für authentifizierte User
CREATE POLICY "Users can view own data"
ON table_name FOR SELECT
TO authenticated
USING (auth.uid() = user_id);
-- Policy für Insert
CREATE POLICY "Users can insert own data"
ON table_name FOR INSERT
TO authenticated
WITH CHECK (auth.uid() = user_id);
```
### 5. TypeScript Types generieren
```bash
# Supabase CLI installieren
pnpm add -D supabase
# Types generieren
npx supabase gen types typescript --project-id YOUR_PROJECT_ID > lib/database.types.ts
```
## Best Practices
1. **Immer RLS aktivieren** - Niemals Tabellen ohne RLS in Production
2. **UUIDs als Primary Keys** - Besser für verteilte Systeme
3. **Timestamps** - created_at/updated_at für Audit Trail
4. **Soft Deletes** - deleted_at statt hartem DELETE
5. **Indexes** - Für WHERE und ORDER BY Spalten
## Beispiel Migration
```sql
-- Create users profile table
CREATE TABLE profiles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL,
username TEXT UNIQUE,
full_name TEXT,
avatar_url TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Enable RLS
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
-- Policies
CREATE POLICY "Public profiles are viewable by everyone"
ON profiles FOR SELECT
USING (true);
CREATE POLICY "Users can update own profile"
ON profiles FOR UPDATE
TO authenticated
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
-- Index
CREATE INDEX profiles_user_id_idx ON profiles(user_id);
-- Updated_at trigger
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER profiles_updated_at
BEFORE UPDATE ON profiles
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
```
---
Frage den Benutzer: Was möchtest du mit der Datenbank machen?
- Neue Tabelle erstellen
- Query schreiben
- RLS Policy einrichten
- Types generieren
- Migration erstellen

View File

@@ -0,0 +1,199 @@
# Supabase Storage Assistent
Du bist ein Experte für Supabase Storage. Hilf dem Benutzer bei File-Upload, Download und Bucket-Management.
## Deine Aufgaben
### 1. Bucket erstellen
```sql
-- Via SQL (Supabase Dashboard)
INSERT INTO storage.buckets (id, name, public)
VALUES ('avatars', 'avatars', true);
-- Oder via CLI
```
```typescript
// Via JavaScript (Admin/Service Role)
const { data, error } = await supabaseAdmin.storage.createBucket('avatars', {
public: true,
fileSizeLimit: 1024 * 1024 * 2, // 2MB
allowedMimeTypes: ['image/png', 'image/jpeg', 'image/webp']
});
```
### 2. File Upload
```typescript
// Client-side Upload
async function uploadFile(file: File, bucket: string, path: string) {
const { data, error } = await supabase.storage
.from(bucket)
.upload(path, file, {
cacheControl: '3600',
upsert: false
});
if (error) throw error;
return data;
}
// Mit Progress
async function uploadWithProgress(file: File, bucket: string, path: string) {
const { data, error } = await supabase.storage
.from(bucket)
.upload(path, file, {
onUploadProgress: (progress) => {
const percent = (progress.loaded / progress.total) * 100;
console.log(`Upload: ${percent.toFixed(0)}%`);
}
});
return { data, error };
}
```
### 3. File Download
```typescript
// Download als Blob
const { data, error } = await supabase.storage
.from('bucket')
.download('path/to/file.pdf');
// Public URL (für öffentliche Buckets)
const { data } = supabase.storage
.from('bucket')
.getPublicUrl('path/to/file.jpg');
// Signed URL (für private Buckets)
const { data, error } = await supabase.storage
.from('bucket')
.createSignedUrl('path/to/file.pdf', 3600); // 1 Stunde gültig
```
### 4. File Management
```typescript
// Liste Dateien
const { data, error } = await supabase.storage
.from('bucket')
.list('folder/', {
limit: 100,
offset: 0,
sortBy: { column: 'name', order: 'asc' }
});
// Datei löschen
const { error } = await supabase.storage
.from('bucket')
.remove(['path/to/file1.jpg', 'path/to/file2.jpg']);
// Datei verschieben/umbenennen
const { error } = await supabase.storage
.from('bucket')
.move('old/path.jpg', 'new/path.jpg');
// Datei kopieren
const { error } = await supabase.storage
.from('bucket')
.copy('source/path.jpg', 'dest/path.jpg');
```
### 5. Storage Policies (RLS)
```sql
-- Öffentlicher Lesezugriff
CREATE POLICY "Public read access"
ON storage.objects FOR SELECT
USING (bucket_id = 'public-bucket');
-- Authentifizierte User können eigene Dateien hochladen
CREATE POLICY "Users can upload own files"
ON storage.objects FOR INSERT
TO authenticated
WITH CHECK (
bucket_id = 'user-files' AND
(storage.foldername(name))[1] = auth.uid()::text
);
-- User können nur eigene Dateien löschen
CREATE POLICY "Users can delete own files"
ON storage.objects FOR DELETE
TO authenticated
USING (
bucket_id = 'user-files' AND
(storage.foldername(name))[1] = auth.uid()::text
);
```
## React Upload Component
```typescript
'use client';
import { useState } from 'react';
import { supabase } from '@/lib/supabase';
interface FileUploadProps {
bucket: string;
onUpload: (url: string) => void;
}
export function FileUpload({ bucket, onUpload }: FileUploadProps) {
const [uploading, setUploading] = useState(false);
const [progress, setProgress] = useState(0);
const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
setUploading(true);
const fileExt = file.name.split('.').pop();
const fileName = `${Date.now()}.${fileExt}`;
const filePath = `uploads/${fileName}`;
const { error } = await supabase.storage
.from(bucket)
.upload(filePath, file);
if (error) {
console.error('Upload error:', error);
} else {
const { data } = supabase.storage.from(bucket).getPublicUrl(filePath);
onUpload(data.publicUrl);
}
setUploading(false);
};
return (
<div>
<input
type="file"
onChange={handleUpload}
disabled={uploading}
/>
{uploading && <span>Uploading...</span>}
</div>
);
}
```
## Best Practices
1. **Bucket-Struktur planen** - z.B. `{user_id}/{type}/{filename}`
2. **File Size Limits setzen** - Verhindert Missbrauch
3. **MIME Types einschränken** - Nur erlaubte Dateitypen
4. **Signed URLs für private Dateien** - Zeitlich begrenzt
5. **CDN nutzen** - Public URLs werden automatisch gecacht
---
Frage den Benutzer: Was möchtest du mit Supabase Storage machen?
- Neuen Bucket erstellen
- Upload Component bauen
- Download implementieren
- Storage Policies einrichten
- Dateien verwalten