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

147
.claude/CLAUDE.md Normal file
View File

@@ -0,0 +1,147 @@
# Lumina Project - Claude Code Kontext
## Projekt-Typ
Full-Stack Web-Applikation erstellt mit dem Lumina AI Development Platform Template.
## Tech Stack
- **Framework:** Next.js 15 (App Router)
- **Runtime:** Node.js 20+
- **Sprache:** TypeScript (strict mode)
- **Styling:** Tailwind CSS
- **UI Components:** Spartan UI
- **Backend/Database:** Supabase (PostgreSQL)
- **Auth:** Supabase Auth
- **Storage:** Supabase Storage
- **Package Manager:** pnpm (bevorzugt) oder npm
## Projekt-Struktur
```
/
├── app/ # Next.js App Router
│ ├── api/ # API Routes
│ ├── (auth)/ # Auth-geschützte Routen
│ ├── layout.tsx # Root Layout
│ ├── page.tsx # Homepage
│ └── globals.css # Globale Styles
├── components/ # React Components
│ ├── ui/ # Basis UI Components
│ └── ... # Feature Components
├── lib/ # Utilities & Services
│ ├── supabase.ts # Supabase Client
│ └── utils.ts # Helper Functions
├── public/ # Static Assets
├── helm/ # Kubernetes Helm Charts
└── .claude/ # Claude Code Konfiguration
```
## Coding Conventions
### TypeScript
- Strict mode aktiviert
- Keine `any` Types - immer typisieren
- Interfaces für Objekte, Types für Unions/Primitives
- Barrel Exports aus index.ts vermeiden
### React/Next.js
- Server Components als Default
- "use client" nur wenn nötig (interaktive Components)
- App Router Patterns verwenden
- Async Components für Data Fetching
### Styling
- Tailwind CSS für alle Styles
- Keine inline styles
- cn() Helper für conditional classes
- Spartan UI Components wenn verfügbar
### Supabase
- Server-Side: Service Role Key für Admin-Operationen
- Client-Side: Anon Key für User-Operationen
- Row Level Security (RLS) immer aktivieren
- Typen aus Database generieren
## Wichtige Patterns
### API Routes
```typescript
// app/api/example/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
try {
// Logic here
return NextResponse.json({ data });
} catch (error) {
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
}
}
```
### Supabase Server Client
```typescript
import { createClient } from '@supabase/supabase-js';
// Server-side mit Service Role
const supabaseAdmin = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
```
### Supabase Client
```typescript
import { supabase } from '@/lib/supabase';
// Client-side Query
const { data, error } = await supabase
.from('table_name')
.select('*')
.eq('column', 'value');
```
## Environment Variables
Erforderlich:
- `NEXT_PUBLIC_SUPABASE_URL` - Supabase Project URL
- `NEXT_PUBLIC_SUPABASE_ANON_KEY` - Supabase Anonymous Key
- `SUPABASE_SERVICE_ROLE_KEY` - Supabase Service Role Key (Server-only)
- `DATABASE_URL` - PostgreSQL Connection String
Optional:
- `REDIS_URL` - Redis Cache
- `ANTHROPIC_API_KEY` - Claude API
- `OPENAI_API_KEY` - OpenAI API
## NPM Scripts
- `pnpm dev` - Development Server (Port 3000)
- `pnpm build` - Production Build
- `pnpm start` - Production Server
- `pnpm lint` - ESLint Check
- `pnpm format` - Prettier Format
## Deployment
Das Projekt unterstützt:
- **Docker:** Dockerfile vorhanden
- **Kubernetes:** Helm Charts in `/helm`
- **Harbor:** Image Registry Integration
## Slash Commands
Verfügbare Claude Code Commands:
- `/supabase-db` - Datenbank-Operationen (CRUD, Migrations, Queries)
- `/supabase-storage` - Storage-Operationen (Upload, Download, Buckets)
- `/supabase-auth` - Authentication Setup & User Management
- `/component` - Neue UI Component erstellen
- `/api` - Neue API Route erstellen
- `/deploy` - Deployment Anleitung
## Hinweise
1. **Sicherheit:** Niemals Service Role Key im Client-Code verwenden
2. **RLS:** Immer Row Level Security Policies definieren
3. **Typen:** Supabase Types mit `supabase gen types` generieren
4. **Migrations:** SQL Migrations in Supabase Dashboard oder via CLI

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

23
.claude/settings.json Normal file
View File

@@ -0,0 +1,23 @@
{
"permissions": {
"allow": [
"Bash(pnpm *)",
"Bash(npm *)",
"Bash(npx *)",
"Bash(docker *)",
"Bash(docker-compose *)",
"Bash(git *)",
"Bash(curl *)",
"Bash(ls *)",
"Bash(cat *)",
"Bash(mkdir *)",
"Bash(cp *)",
"Bash(mv *)",
"WebSearch"
],
"deny": [
"Bash(rm -rf /)",
"Bash(sudo *)"
]
}
}

View File

@@ -0,0 +1,114 @@
---
name: dependency-scanner
description: Scannt npm Dependencies auf bekannte Sicherheitsluecken und veraltete Pakete. Nutze diesen Skill nach npm install, bei neuen Dependencies, oder wenn der User nach "vulnerabilities", "outdated", "npm audit", "dependency check", "pakete pruefen", "sicherheitsluecken" fragt. Gibt Ergebnisse zurueck die im Chat angezeigt werden.
---
# Dependency Security Scanner
Scannt npm/pnpm Dependencies auf Sicherheitsluecken und veraltete Pakete.
## Automatische Ausfuehrung
Fuehre diesen Scan aus:
1. Nach `npm install` oder `pnpm install`
2. Bei neuen Dependencies in package.json
3. Regelmaessig als Security Check
## Scripts
### Vollstaendiger Scan
```bash
bash scan-deps.sh
```
### Nur Audit (Vulnerabilities)
```bash
bash audit.sh
```
### Veraltete Pakete pruefen
```bash
bash check-outdated.sh
```
### Automatisch fixen
```bash
bash fix-vulnerabilities.sh
```
## Scan Ablauf
### 1. npm audit durchfuehren
```bash
# Mit pnpm
pnpm audit --json
# Mit npm
npm audit --json
```
### 2. Veraltete Pakete pruefen
```bash
# Mit pnpm
pnpm outdated --json
# Mit npm
npm outdated --json
```
### 3. License Check (optional)
```bash
npx license-checker --summary
```
## Report Format fuer Chat
Gib das Ergebnis in diesem Format zurueck:
```markdown
## Dependency Security Report
**Scan Datum**: [timestamp]
**Pakete geprueft**: [count]
### Vulnerabilities
| Severity | Count |
|----------|-------|
| Critical | 0 |
| High | 2 |
| Moderate | 5 |
| Low | 3 |
### Kritische Issues
1. **lodash** (4.17.15) - Prototype Pollution
- Fix: `pnpm update lodash`
2. **axios** (0.21.0) - SSRF Vulnerability
- Fix: `pnpm update axios`
### Veraltete Pakete
| Paket | Aktuell | Neueste | Typ |
|-------|---------|---------|-----|
| react | 18.2.0 | 19.0.0 | major |
| next | 14.0.0 | 15.1.0 | major |
### Empfehlungen
1. **Sofort**: Kritische Vulnerabilities fixen
2. **Diese Woche**: High Severity fixen
3. **Geplant**: Major Updates evaluieren
### Automatischer Fix
Fuehre aus: `pnpm audit fix` oder `bash fix-vulnerabilities.sh`
```
## Wichtig
- Zeige Ergebnisse IMMER im Chat an
- Bei kritischen Vulnerabilities: Warnung hervorheben
- Schlage konkrete Fix-Befehle vor
- Bei Major Updates: Changelog verlinken

View File

@@ -0,0 +1,16 @@
#!/bin/bash
# Schneller Vulnerability Audit
set -e
# Package Manager erkennen
if [ -f "pnpm-lock.yaml" ]; then
echo "Fuehre pnpm audit aus..."
pnpm audit
elif [ -f "yarn.lock" ]; then
echo "Fuehre yarn audit aus..."
yarn audit
else
echo "Fuehre npm audit aus..."
npm audit
fi

View File

@@ -0,0 +1,22 @@
#!/bin/bash
# Prueft auf veraltete Pakete
set -e
echo "Pruefe veraltete Pakete..."
echo ""
# Package Manager erkennen
if [ -f "pnpm-lock.yaml" ]; then
pnpm outdated || true
elif [ -f "yarn.lock" ]; then
yarn outdated || true
else
npm outdated || true
fi
echo ""
echo "Zum Updaten:"
echo " Alle: pnpm update"
echo " Einzeln: pnpm update <paket>"
echo " Major: pnpm update <paket>@latest"

View File

@@ -0,0 +1,60 @@
#!/bin/bash
# Automatischer Fix fuer Vulnerabilities
set -e
# Farben
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
echo "Dependency Vulnerability Fix"
echo "============================"
echo ""
# Package Manager erkennen
if [ -f "pnpm-lock.yaml" ]; then
PKG_MANAGER="pnpm"
elif [ -f "yarn.lock" ]; then
PKG_MANAGER="yarn"
else
PKG_MANAGER="npm"
fi
# Vorher: Audit Status
echo -e "${YELLOW}Status vor Fix:${NC}"
$PKG_MANAGER audit 2>/dev/null | tail -5 || true
echo ""
# Fix durchfuehren
echo -e "${YELLOW}Fuehre automatischen Fix durch...${NC}"
echo ""
case "$PKG_MANAGER" in
"pnpm")
# pnpm hat kein direktes audit fix
echo "pnpm: Update betroffene Pakete..."
pnpm update
;;
"yarn")
yarn audit fix || yarn upgrade
;;
"npm")
npm audit fix
;;
esac
echo ""
# Nachher: Audit Status
echo -e "${YELLOW}Status nach Fix:${NC}"
$PKG_MANAGER audit 2>/dev/null | tail -5 || true
echo ""
echo -e "${GREEN}Fix abgeschlossen!${NC}"
echo ""
echo "Naechste Schritte:"
echo " 1. Teste die Anwendung: pnpm dev"
echo " 2. Fuehre Tests aus: pnpm test"
echo " 3. Bei Problemen: git checkout package.json pnpm-lock.yaml"

View File

@@ -0,0 +1,160 @@
#!/bin/bash
# Vollstaendiger Dependency Security Scan
set -e
# Farben
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m'
echo -e "${BLUE}================================${NC}"
echo -e "${BLUE}Dependency Security Scanner${NC}"
echo -e "${BLUE}================================${NC}"
echo ""
# Package Manager erkennen
if [ -f "pnpm-lock.yaml" ]; then
PKG_MANAGER="pnpm"
elif [ -f "yarn.lock" ]; then
PKG_MANAGER="yarn"
elif [ -f "package-lock.json" ]; then
PKG_MANAGER="npm"
else
PKG_MANAGER="npm"
fi
echo "Package Manager: $PKG_MANAGER"
echo ""
# Temporaere Dateien
AUDIT_FILE=$(mktemp)
OUTDATED_FILE=$(mktemp)
# ================================
# 1. VULNERABILITY AUDIT
# ================================
echo -e "${YELLOW}[1/3] Pruefe Sicherheitsluecken...${NC}"
case "$PKG_MANAGER" in
"pnpm")
pnpm audit --json > "$AUDIT_FILE" 2>/dev/null || true
;;
"yarn")
yarn audit --json > "$AUDIT_FILE" 2>/dev/null || true
;;
"npm")
npm audit --json > "$AUDIT_FILE" 2>/dev/null || true
;;
esac
# Audit Ergebnisse parsen
if [ -s "$AUDIT_FILE" ]; then
CRITICAL=$(jq '.metadata.vulnerabilities.critical // 0' "$AUDIT_FILE" 2>/dev/null || echo "0")
HIGH=$(jq '.metadata.vulnerabilities.high // 0' "$AUDIT_FILE" 2>/dev/null || echo "0")
MODERATE=$(jq '.metadata.vulnerabilities.moderate // 0' "$AUDIT_FILE" 2>/dev/null || echo "0")
LOW=$(jq '.metadata.vulnerabilities.low // 0' "$AUDIT_FILE" 2>/dev/null || echo "0")
TOTAL=$((CRITICAL + HIGH + MODERATE + LOW))
else
CRITICAL=0
HIGH=0
MODERATE=0
LOW=0
TOTAL=0
fi
echo ""
echo "Vulnerabilities gefunden:"
echo -e " ${RED}Critical: $CRITICAL${NC}"
echo -e " ${RED}High: $HIGH${NC}"
echo -e " ${YELLOW}Moderate: $MODERATE${NC}"
echo -e " Low: $LOW"
echo ""
# ================================
# 2. OUTDATED CHECK
# ================================
echo -e "${YELLOW}[2/3] Pruefe veraltete Pakete...${NC}"
case "$PKG_MANAGER" in
"pnpm")
pnpm outdated --json > "$OUTDATED_FILE" 2>/dev/null || true
;;
"npm")
npm outdated --json > "$OUTDATED_FILE" 2>/dev/null || true
;;
"yarn")
yarn outdated --json > "$OUTDATED_FILE" 2>/dev/null || true
;;
esac
if [ -s "$OUTDATED_FILE" ]; then
OUTDATED_COUNT=$(jq 'length' "$OUTDATED_FILE" 2>/dev/null || echo "0")
echo "Veraltete Pakete: $OUTDATED_COUNT"
if [ "$OUTDATED_COUNT" -gt 0 ]; then
echo ""
echo "Top 10 veraltete Pakete:"
jq -r 'to_entries | .[:10][] | " \(.key): \(.value.current // "?") -> \(.value.latest // "?")"' "$OUTDATED_FILE" 2>/dev/null || true
fi
else
OUTDATED_COUNT=0
echo "Keine veralteten Pakete gefunden."
fi
echo ""
# ================================
# 3. SCORE BERECHNEN
# ================================
echo -e "${YELLOW}[3/3] Berechne Security Score...${NC}"
SCORE=$((100 - (CRITICAL * 25) - (HIGH * 10) - (MODERATE * 3) - (LOW * 1)))
if [ $SCORE -lt 0 ]; then SCORE=0; fi
echo ""
echo -e "${BLUE}================================${NC}"
echo -e "${BLUE}ERGEBNIS${NC}"
echo -e "${BLUE}================================${NC}"
echo ""
if [ $SCORE -ge 90 ]; then
echo -e "Security Score: ${GREEN}$SCORE/100${NC}"
elif [ $SCORE -ge 70 ]; then
echo -e "Security Score: ${YELLOW}$SCORE/100${NC}"
else
echo -e "Security Score: ${RED}$SCORE/100${NC}"
fi
echo ""
echo "Zusammenfassung:"
echo " Vulnerabilities: $TOTAL"
echo " Veraltete Pakete: $OUTDATED_COUNT"
# ================================
# EMPFEHLUNGEN
# ================================
echo ""
echo -e "${BLUE}Empfehlungen:${NC}"
if [ "$CRITICAL" -gt 0 ] || [ "$HIGH" -gt 0 ]; then
echo -e " ${RED}DRINGEND: Fuehre '$PKG_MANAGER audit fix' aus${NC}"
fi
if [ "$OUTDATED_COUNT" -gt 10 ]; then
echo -e " ${YELLOW}Updates verfuegbar: '$PKG_MANAGER update'${NC}"
fi
if [ "$TOTAL" -eq 0 ] && [ "$OUTDATED_COUNT" -lt 5 ]; then
echo -e " ${GREEN}Alles in Ordnung! Dependencies sind sicher.${NC}"
fi
# Cleanup
rm -f "$AUDIT_FILE" "$OUTDATED_FILE"
# Exit mit Fehler bei kritischen Issues
if [ "$CRITICAL" -gt 0 ]; then
exit 1
fi

View File

@@ -0,0 +1,55 @@
---
name: postgres-connect
description: Verbindet mit der PostgreSQL Datenbank. Erkennt das Betriebssystem, installiert psql CLI falls noetig, liest DATABASE_URL aus .env und stellt Verbindung her. Nutze diesen Skill wenn der User "Datenbank verbinden", "DB Verbindung", "psql", "PostgreSQL connect" oder aehnliches erwaehnt.
---
# PostgreSQL Datenbank Verbindung
Dieser Skill verbindet automatisch mit der PostgreSQL Datenbank des Projekts.
## Automatischer Ablauf
1. **OS erkennen** mit dem Script `detect-os.sh`
2. **psql pruefen** - ist PostgreSQL CLI installiert?
3. **Falls nicht installiert** - Installation je nach OS
4. **DATABASE_URL laden** aus `.env.local` oder `.env`
5. **Verbindung herstellen** mit psql
## Scripts verwenden
### OS-Erkennung
```bash
bash detect-os.sh
```
### psql Installation
```bash
bash install-psql.sh
```
### Verbindung herstellen
```bash
bash connect.sh
```
## Manuelle Befehle nach Verbindung
```sql
-- Alle Tabellen anzeigen
\dt
-- Tabellen-Schema
\d table_name
-- Supabase Auth Users
SELECT id, email, created_at FROM auth.users LIMIT 10;
-- RLS Policies pruefen
SELECT * FROM pg_policies WHERE schemaname = 'public';
```
## Troubleshooting
- **Connection refused**: Supabase Projekt evtl. pausiert
- **SSL required**: `?sslmode=require` an URL anhaengen
- **Auth failed**: Passwort URL-encoded? (@ -> %40)

View File

@@ -0,0 +1,36 @@
#!/bin/bash
# Testet die Datenbankverbindung ohne interaktive Shell
set -e
# DATABASE_URL laden
if [ -f .env.local ]; then
DATABASE_URL=$(grep -E "^DATABASE_URL=" .env.local | cut -d '=' -f2- | tr -d '"' | tr -d "'")
fi
if [ -z "$DATABASE_URL" ] && [ -f .env ]; then
DATABASE_URL=$(grep -E "^DATABASE_URL=" .env | cut -d '=' -f2- | tr -d '"' | tr -d "'")
fi
if [ -z "$DATABASE_URL" ]; then
echo "DATABASE_URL nicht gefunden"
exit 1
fi
# Verbindung testen
echo "Teste Verbindung..."
if psql "$DATABASE_URL" -c "SELECT 1;" > /dev/null 2>&1; then
echo "Verbindung erfolgreich!"
# Zusaetzliche Infos
echo ""
echo "Datenbank-Info:"
psql "$DATABASE_URL" -c "SELECT current_database() as database, current_user as user, version();" 2>/dev/null
echo ""
echo "Tabellen:"
psql "$DATABASE_URL" -c "\dt" 2>/dev/null || echo "(keine Tabellen gefunden)"
else
echo "Verbindung fehlgeschlagen!"
exit 1
fi

View File

@@ -0,0 +1,27 @@
#!/bin/bash
# Verbindet mit der PostgreSQL Datenbank aus .env
set -e
# DATABASE_URL aus .env.local oder .env laden
if [ -f .env.local ]; then
DATABASE_URL=$(grep -E "^DATABASE_URL=" .env.local | cut -d '=' -f2- | tr -d '"' | tr -d "'")
fi
if [ -z "$DATABASE_URL" ] && [ -f .env ]; then
DATABASE_URL=$(grep -E "^DATABASE_URL=" .env | cut -d '=' -f2- | tr -d '"' | tr -d "'")
fi
if [ -z "$DATABASE_URL" ]; then
echo "Fehler: DATABASE_URL nicht in .env.local oder .env gefunden."
echo ""
echo "Bitte fuege DATABASE_URL zu .env.local hinzu:"
echo 'DATABASE_URL="postgresql://user:password@host:port/database"'
exit 1
fi
echo "Verbinde mit Datenbank..."
echo "URL: ${DATABASE_URL%%@*}@***" # URL ohne Passwort ausgeben
# Verbindung herstellen
psql "$DATABASE_URL"

View File

@@ -0,0 +1,28 @@
#!/bin/bash
# Erkennt das Betriebssystem und gibt es aus
OS_TYPE=$(uname -s)
case "$OS_TYPE" in
"Darwin")
echo "macos"
;;
"Linux")
# Unterscheide zwischen verschiedenen Linux-Distributionen
if [ -f /etc/alpine-release ]; then
echo "alpine"
elif [ -f /etc/debian_version ]; then
echo "debian"
elif [ -f /etc/redhat-release ]; then
echo "redhat"
else
echo "linux"
fi
;;
"MINGW"*|"MSYS"*|"CYGWIN"*)
echo "windows"
;;
*)
echo "unknown"
;;
esac

View File

@@ -0,0 +1,72 @@
#!/bin/bash
# Installiert PostgreSQL CLI basierend auf dem Betriebssystem
set -e
# OS erkennen
OS_TYPE=$(uname -s)
# Pruefen ob psql bereits installiert ist
if command -v psql &> /dev/null; then
echo "psql ist bereits installiert: $(psql --version)"
exit 0
fi
echo "psql nicht gefunden. Starte Installation..."
case "$OS_TYPE" in
"Darwin")
# macOS - mit Homebrew
if ! command -v brew &> /dev/null; then
echo "Homebrew nicht gefunden. Bitte installiere Homebrew zuerst:"
echo '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
exit 1
fi
echo "Installiere PostgreSQL via Homebrew..."
brew install postgresql
;;
"Linux")
if [ -f /etc/alpine-release ]; then
# Alpine Linux
echo "Installiere PostgreSQL Client auf Alpine..."
apk add --no-cache postgresql-client
elif [ -f /etc/debian_version ]; then
# Debian/Ubuntu
echo "Installiere PostgreSQL Client auf Debian/Ubuntu..."
sudo apt-get update
sudo apt-get install -y postgresql-client
elif [ -f /etc/redhat-release ]; then
# RHEL/CentOS/Fedora
echo "Installiere PostgreSQL Client auf RHEL/CentOS..."
sudo yum install -y postgresql
else
echo "Unbekannte Linux-Distribution. Bitte installiere postgresql-client manuell."
exit 1
fi
;;
"MINGW"*|"MSYS"*|"CYGWIN"*)
# Windows
echo "Windows erkannt."
echo "Bitte installiere PostgreSQL von: https://www.postgresql.org/download/windows/"
echo "Oder nutze: winget install PostgreSQL.PostgreSQL"
exit 1
;;
*)
echo "Unbekanntes Betriebssystem: $OS_TYPE"
exit 1
;;
esac
# Verifizieren
if command -v psql &> /dev/null; then
echo "Installation erfolgreich: $(psql --version)"
else
echo "Installation fehlgeschlagen."
exit 1
fi

View File

@@ -0,0 +1,148 @@
---
name: semgrep-security
description: Security Scanner mit Semgrep. WICHTIG - Dieser Skill wird automatisch bei JEDER Code-Implementierung aktiviert. Scannt neuen und geaenderten Code auf Sicherheitsluecken, Vulnerabilities und Best Practice Verstoesse. Aktiviert bei Begriffen wie "implementieren", "code schreiben", "feature erstellen", "komponente bauen", "funktion erstellen", "api route", "fertig", "done", "commit", "security", "scan", "vulnerabilities", "sicherheit".
---
# Semgrep Security Scanner
Dieser Skill scannt automatisch Code auf Sicherheitsprobleme nach jeder Implementierung.
## WICHTIG: Automatische Ausfuehrung
Nach JEDER Code-Implementierung fuehre folgende Schritte aus:
1. **Semgrep installieren** (falls nicht vorhanden)
2. **Geaenderte Dateien scannen**
3. **Probleme fixen oder reporten**
## Scripts
### Installation pruefen und durchfuehren
```bash
bash install-semgrep.sh
```
### Scan durchfuehren
```bash
# Alle Dateien scannen
bash scan.sh
# Nur bestimmte Dateien scannen
bash scan.sh app/api/users/route.ts components/UserForm.tsx
```
### Autofix anwenden
```bash
bash autofix.sh
```
## Workflow nach Implementierung
### 1. Installation sicherstellen
```bash
# Pruefen ob semgrep installiert ist
which semgrep || bash install-semgrep.sh
```
### 2. Geaenderte Dateien ermitteln
```bash
# Untracked und modified files
git status --porcelain | grep -E '^\?\?|^ M|^M' | cut -c4-
```
### 3. Security Scan ausfuehren
```bash
# Mit Auto-Config (empfohlen)
semgrep --config=auto --json .
# Fuer TypeScript/React spezifisch
semgrep --config=p/typescript --config=p/react --json .
# OWASP Top 10
semgrep --config=p/owasp-top-ten --json .
```
### 4. Ergebnisse analysieren und fixen
```bash
# Mit Autofix
semgrep --config=auto --autofix .
# Nur Report ohne Fix
semgrep --config=auto --sarif -o results.sarif .
```
## Haeufige Security Issues und Fixes
### SQL Injection
```typescript
// SCHLECHT
const query = `SELECT * FROM users WHERE id = ${userId}`;
// GUT
const { data } = await supabase.from('users').select('*').eq('id', userId);
```
### XSS (Cross-Site Scripting)
```typescript
// SCHLECHT
<div dangerouslySetInnerHTML={{ __html: userInput }} />
// GUT
<div>{sanitize(userInput)}</div>
```
### Hardcoded Secrets
```typescript
// SCHLECHT
const apiKey = "sk-1234567890";
// GUT
const apiKey = process.env.API_KEY;
```
### Insecure Direct Object Reference
```typescript
// SCHLECHT - Keine Auth-Pruefung
const user = await getUser(req.query.id);
// GUT - Mit RLS oder Auth-Check
const { data: { user } } = await supabase.auth.getUser();
const profile = await supabase.from('profiles').select().eq('user_id', user.id);
```
## Report Format
Nach jedem Scan zeige:
```markdown
## Security Scan Ergebnisse
**Score**: 85/100
| Severity | Count |
|----------|-------|
| Critical | 0 |
| High | 1 |
| Medium | 3 |
| Low | 5 |
### Gefundene Issues
1. **[HIGH] Potential XSS** in `components/Comment.tsx:42`
- Problem: Unescaped user input
- Fix: Verwende DOMPurify oder sanitize-html
### Automatisch gefixt
- 2 Issues wurden automatisch behoben
### Naechste Schritte
- [ ] Issue #1 manuell pruefen
```
## Best Practices
1. **Immer scannen** nach Code-Aenderungen
2. **Autofix nutzen** fuer einfache Issues
3. **Kritische Issues** sofort beheben
4. **False Positives** in `.semgrepignore` ausschliessen
5. **CI/CD Integration** fuer automatische Scans

View File

@@ -0,0 +1,54 @@
#!/bin/bash
# Fuehrt Semgrep Autofix durch
set -e
# Farben
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Pruefen ob Semgrep installiert ist
if ! command -v semgrep &> /dev/null; then
echo -e "${YELLOW}Semgrep nicht gefunden. Installiere...${NC}"
bash "$(dirname "$0")/install-semgrep.sh"
fi
# Argumente
if [ $# -gt 0 ]; then
SCAN_TARGET="$@"
else
SCAN_TARGET="."
fi
echo "Starte Semgrep Autofix..."
echo "========================="
echo ""
# Autofix durchfuehren
# Nur Rules mit Autofix-Support
semgrep \
--config=auto \
--autofix \
--dryrun \
$SCAN_TARGET 2>&1 | head -50
echo ""
read -p "Fixes anwenden? (y/n) " -n 1 -r
echo ""
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "Wende Fixes an..."
semgrep \
--config=auto \
--autofix \
$SCAN_TARGET
echo ""
echo -e "${GREEN}Fixes angewendet!${NC}"
echo ""
echo "Bitte pruefen Sie die Aenderungen:"
echo " git diff"
else
echo "Autofix abgebrochen."
fi

View File

@@ -0,0 +1,86 @@
#!/bin/bash
# Installiert Semgrep basierend auf dem Betriebssystem
set -e
echo "Pruefe Semgrep Installation..."
# Pruefen ob bereits installiert
if command -v semgrep &> /dev/null; then
echo "Semgrep ist bereits installiert: $(semgrep --version)"
exit 0
fi
echo "Semgrep nicht gefunden. Starte Installation..."
# OS erkennen
OS_TYPE=$(uname -s)
case "$OS_TYPE" in
"Darwin")
# macOS
echo "macOS erkannt - nutze Homebrew"
if command -v brew &> /dev/null; then
brew install semgrep
else
echo "Homebrew nicht gefunden - nutze pip3"
pip3 install semgrep
fi
;;
"Linux")
echo "Linux erkannt"
# Versuche verschiedene Paketmanager
if command -v apt-get &> /dev/null; then
echo "Nutze apt-get..."
# Semgrep via Python da apt Version oft veraltet
pip3 install semgrep
elif command -v apk &> /dev/null; then
echo "Alpine erkannt - nutze pip3"
apk add --no-cache python3 py3-pip
pip3 install semgrep
elif command -v yum &> /dev/null; then
echo "RHEL/CentOS erkannt - nutze pip3"
pip3 install semgrep
else
echo "Nutze pip3 als Fallback"
pip3 install semgrep
fi
;;
"MINGW"*|"MSYS"*|"CYGWIN"*)
# Windows
echo "Windows erkannt"
if command -v choco &> /dev/null; then
choco install semgrep -y
elif command -v pip3 &> /dev/null; then
pip3 install semgrep
elif command -v pip &> /dev/null; then
pip install semgrep
else
echo "Bitte installiere Python und pip: https://www.python.org/downloads/"
exit 1
fi
;;
*)
echo "Unbekanntes OS: $OS_TYPE - nutze pip3"
pip3 install semgrep
;;
esac
# Verifizieren
if command -v semgrep &> /dev/null; then
echo ""
echo "Installation erfolgreich!"
semgrep --version
else
echo ""
echo "Installation fehlgeschlagen."
echo "Bitte manuell installieren: https://semgrep.dev/docs/getting-started/"
exit 1
fi

View File

@@ -0,0 +1,91 @@
# Custom Semgrep Rules fuer Next.js Security
rules:
# Verhindere dangerouslySetInnerHTML ohne Sanitization
- id: next-xss-dangerous-html
patterns:
- pattern: dangerouslySetInnerHTML={{ __html: $VAR }}
- pattern-not: dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize($VAR) }}
- pattern-not: dangerouslySetInnerHTML={{ __html: sanitize($VAR) }}
message: "XSS Risiko: Verwende DOMPurify.sanitize() bevor du dangerouslySetInnerHTML nutzt"
severity: ERROR
languages: [typescript, javascript]
# Verhindere hardcoded API Keys
- id: next-hardcoded-api-key
patterns:
- pattern-either:
- pattern: |
$KEY = "sk-..."
- pattern: |
$KEY = "pk_..."
- pattern: |
apiKey: "..."
- pattern: |
api_key = "..."
message: "Hardcoded API Key gefunden. Verwende Environment Variables."
severity: ERROR
languages: [typescript, javascript]
# Verhindere ungeschuetzte API Routes
- id: next-unprotected-api-route
patterns:
- pattern: |
export async function $METHOD(request: NextRequest) {
...
$DB.$OPERATION(...)
...
}
- pattern-not: |
export async function $METHOD(request: NextRequest) {
...
auth.getUser()
...
}
- pattern-not: |
export async function $METHOD(request: NextRequest) {
...
getSession()
...
}
message: "API Route ohne Authentication. Fuege Auth-Check hinzu."
severity: WARNING
languages: [typescript]
paths:
include:
- "app/api/**"
# Verhindere Secrets in Client Components
- id: next-secret-in-client
patterns:
- pattern-inside: |
"use client"
...
- pattern-either:
- pattern: process.env.SUPABASE_SERVICE_ROLE_KEY
- pattern: process.env.DATABASE_URL
- pattern: process.env.$SECRET_KEY
message: "Server-only Secret in Client Component. Verschiebe in Server Component oder API Route."
severity: ERROR
languages: [typescript, javascript]
# Verhindere eval und new Function
- id: next-no-eval
patterns:
- pattern-either:
- pattern: eval(...)
- pattern: new Function(...)
message: "eval() und new Function() sind Sicherheitsrisiken. Finde eine Alternative."
severity: ERROR
languages: [typescript, javascript]
# Supabase RLS Check
- id: supabase-rls-bypass
patterns:
- pattern: supabaseAdmin.from($TABLE)
- pattern-not-inside: |
// RLS bypassed intentionally
...
message: "supabaseAdmin umgeht RLS. Stelle sicher dass dies beabsichtigt ist."
severity: WARNING
languages: [typescript, javascript]

View File

@@ -0,0 +1,29 @@
#!/bin/bash
# Scannt nur geaenderte Dateien (staged + unstaged)
set -e
# Farben
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
echo "Ermittle geaenderte Dateien..."
# Geaenderte Dateien ermitteln (TypeScript/JavaScript)
CHANGED_FILES=$(git status --porcelain 2>/dev/null | \
grep -E '\.(ts|tsx|js|jsx)$' | \
sed 's/^...//' | \
tr '\n' ' ')
if [ -z "$CHANGED_FILES" ]; then
echo -e "${GREEN}Keine geaenderten TypeScript/JavaScript Dateien gefunden.${NC}"
exit 0
fi
echo "Geaenderte Dateien:"
echo "$CHANGED_FILES" | tr ' ' '\n' | grep -v '^$'
echo ""
# Scan durchfuehren
bash "$(dirname "$0")/scan.sh" $CHANGED_FILES

View File

@@ -0,0 +1,89 @@
#!/bin/bash
# Fuehrt Semgrep Security Scan durch
set -e
# Farben fuer Output
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color
# Pruefen ob Semgrep installiert ist
if ! command -v semgrep &> /dev/null; then
echo -e "${YELLOW}Semgrep nicht gefunden. Installiere...${NC}"
bash "$(dirname "$0")/install-semgrep.sh"
fi
# Argumente: spezifische Dateien oder alles scannen
if [ $# -gt 0 ]; then
SCAN_TARGET="$@"
echo "Scanne spezifische Dateien: $SCAN_TARGET"
else
SCAN_TARGET="."
echo "Scanne gesamtes Projekt..."
fi
# Temporaere Datei fuer JSON Output
RESULT_FILE=$(mktemp)
# Scan durchfuehren
echo ""
echo "Starte Security Scan..."
echo "========================"
semgrep \
--config=auto \
--config=p/security-audit \
--config=p/typescript \
--json \
--output="$RESULT_FILE" \
$SCAN_TARGET 2>/dev/null || true
# Ergebnisse parsen
if [ -f "$RESULT_FILE" ]; then
# Anzahl der Findings zaehlen
TOTAL=$(jq '.results | length' "$RESULT_FILE" 2>/dev/null || echo "0")
ERRORS=$(jq '[.results[] | select(.extra.severity == "ERROR")] | length' "$RESULT_FILE" 2>/dev/null || echo "0")
WARNINGS=$(jq '[.results[] | select(.extra.severity == "WARNING")] | length' "$RESULT_FILE" 2>/dev/null || echo "0")
INFO=$(jq '[.results[] | select(.extra.severity == "INFO")] | length' "$RESULT_FILE" 2>/dev/null || echo "0")
echo ""
echo "========================"
echo "Scan Ergebnisse"
echo "========================"
echo ""
if [ "$TOTAL" -eq 0 ]; then
echo -e "${GREEN}Keine Sicherheitsprobleme gefunden!${NC}"
else
echo -e "Gefunden: ${RED}$ERRORS Critical/High${NC}, ${YELLOW}$WARNINGS Medium${NC}, $INFO Low"
echo ""
# Details ausgeben
echo "Details:"
echo "--------"
jq -r '.results[] | "[\(.extra.severity)] \(.check_id)\n File: \(.path):\(.start.line)\n Message: \(.extra.message)\n"' "$RESULT_FILE" 2>/dev/null || true
fi
# Score berechnen
SCORE=$((100 - (ERRORS * 10) - (WARNINGS * 3) - (INFO * 1)))
if [ $SCORE -lt 0 ]; then SCORE=0; fi
echo ""
echo "========================"
echo -e "Security Score: ${GREEN}$SCORE/100${NC}"
echo "========================"
# Cleanup
rm -f "$RESULT_FILE"
# Exit mit Fehler wenn kritische Issues
if [ "$ERRORS" -gt 0 ]; then
echo ""
echo -e "${RED}WARNUNG: Es wurden kritische Sicherheitsprobleme gefunden!${NC}"
exit 1
fi
else
echo -e "${GREEN}Scan abgeschlossen - keine Ergebnisdatei erstellt${NC}"
fi

View File

@@ -0,0 +1,152 @@
---
name: supabase-auth
description: Supabase Authentication Setup und User Management. Nutze diesen Skill fuer Login, Signup, OAuth, Sessions, Password Reset und geschuetzte Routen. Aktiviert bei Begriffen wie "Login", "Anmeldung", "Registrierung", "Signup", "Auth", "Authentication", "User", "Benutzer", "Session", "Passwort", "OAuth", "Google Login", "geschuetzt", "protected route".
---
# Supabase Authentication Skill
Dieser Skill hilft bei Authentication mit Supabase.
## Auth Client Setup
```typescript
// lib/supabase.ts - Browser Client
import { createClient } from '@supabase/supabase-js';
export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
// lib/supabase-admin.ts - Server Client
export const supabaseAdmin = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!,
{
auth: {
autoRefreshToken: false,
persistSession: false
}
}
);
```
## Sign Up / Sign In
```typescript
// Email/Password Signup
const { data, error } = await supabase.auth.signUp({
email: 'user@example.com',
password: 'secure-password',
options: {
data: { full_name: 'Max Mustermann' }
}
});
// Email/Password Login
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`
}
});
```
## Session Management
```typescript
// Aktuelle Session
const { data: { session } } = await supabase.auth.getSession();
// Aktueller User
const { data: { user } } = await supabase.auth.getUser();
// Auth State Listener
supabase.auth.onAuthStateChange((event, session) => {
console.log(event, session);
});
// Logout
await supabase.auth.signOut();
```
## Password Reset
```typescript
// Reset anfordern
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${window.location.origin}/auth/reset-password`
});
// Neues Passwort setzen
const { error } = await supabase.auth.updateUser({
password: 'new-password'
});
```
## Middleware (Protected Routes)
```typescript
// middleware.ts
import { createServerClient } from '@supabase/ssr';
import { NextResponse, type NextRequest } from 'next/server';
export async function middleware(request: NextRequest) {
let response = NextResponse.next({ request });
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get: (name) => request.cookies.get(name)?.value,
set: (name, value, options) => {
response.cookies.set({ name, value, ...options });
},
remove: (name, options) => {
response.cookies.set({ name, value: '', ...options });
},
},
}
);
const { data: { session } } = await supabase.auth.getSession();
// Redirect wenn nicht eingeloggt
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*']
};
```
## Auth Context
Siehe `templates/AuthContext.tsx` fuer einen kompletten Auth Provider.
## Best Practices
1. SSR Auth mit @supabase/ssr
2. Middleware fuer Route Protection
3. Service Role Key nur serverseitig
4. User-freundliche Fehlermeldungen
5. Email Verification aktivieren

View File

@@ -0,0 +1,108 @@
'use client';
import { createContext, useContext, useEffect, useState, useCallback } from 'react';
import { User, Session, AuthError } from '@supabase/supabase-js';
import { supabase } from '@/lib/supabase';
interface AuthContextType {
user: User | null;
session: Session | null;
loading: boolean;
signUp: (email: string, password: string, metadata?: Record<string, unknown>) => Promise<{ error: AuthError | null }>;
signIn: (email: string, password: string) => Promise<{ error: AuthError | null }>;
signInWithOAuth: (provider: 'google' | 'github' | 'azure') => Promise<void>;
signOut: () => Promise<void>;
resetPassword: (email: string) => Promise<{ error: AuthError | null }>;
}
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(() => {
// Initiale Session holen
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session);
setUser(session?.user ?? null);
setLoading(false);
});
// Auth State Listener
const { data: { subscription } } = supabase.auth.onAuthStateChange(
(_event, session) => {
setSession(session);
setUser(session?.user ?? null);
}
);
return () => subscription.unsubscribe();
}, []);
const signUp = useCallback(
async (email: string, password: string, metadata?: Record<string, unknown>) => {
const { error } = await supabase.auth.signUp({
email,
password,
options: { data: metadata },
});
return { error };
},
[]
);
const signIn = useCallback(async (email: string, password: string) => {
const { error } = await supabase.auth.signInWithPassword({
email,
password,
});
return { error };
}, []);
const signInWithOAuth = useCallback(async (provider: 'google' | 'github' | 'azure') => {
await supabase.auth.signInWithOAuth({
provider,
options: {
redirectTo: `${window.location.origin}/auth/callback`,
},
});
}, []);
const signOut = useCallback(async () => {
await supabase.auth.signOut();
}, []);
const resetPassword = useCallback(async (email: string) => {
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${window.location.origin}/auth/reset-password`,
});
return { error };
}, []);
return (
<AuthContext.Provider
value={{
user,
session,
loading,
signUp,
signIn,
signInWithOAuth,
signOut,
resetPassword,
}}
>
{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;
}

View File

@@ -0,0 +1,123 @@
'use client';
import { useState } from 'react';
import { useAuth } from '@/contexts/AuthContext';
interface LoginFormProps {
onSuccess?: () => void;
redirectTo?: string;
}
export function LoginForm({ onSuccess, redirectTo = '/dashboard' }: LoginFormProps) {
const { signIn, signInWithOAuth } = useAuth();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError(null);
const { error } = await signIn(email, password);
if (error) {
setError(getErrorMessage(error.message));
setLoading(false);
} else {
onSuccess?.();
window.location.href = redirectTo;
}
};
const handleOAuth = async (provider: 'google' | 'github') => {
setLoading(true);
await signInWithOAuth(provider);
};
return (
<div className="space-y-6 w-full max-w-sm">
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="email" className="block text-sm font-medium mb-1">
Email
</label>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-3 py-2 border rounded-md"
required
/>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium mb-1">
Passwort
</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-3 py-2 border rounded-md"
required
/>
</div>
{error && (
<p className="text-sm text-red-500">{error}</p>
)}
<button
type="submit"
disabled={loading}
className="w-full py-2 px-4 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 disabled:opacity-50"
>
{loading ? 'Wird geladen...' : 'Anmelden'}
</button>
</form>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
Oder weiter mit
</span>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<button
type="button"
onClick={() => handleOAuth('google')}
disabled={loading}
className="py-2 px-4 border rounded-md hover:bg-muted disabled:opacity-50"
>
Google
</button>
<button
type="button"
onClick={() => handleOAuth('github')}
disabled={loading}
className="py-2 px-4 border rounded-md hover:bg-muted disabled:opacity-50"
>
GitHub
</button>
</div>
</div>
);
}
function getErrorMessage(message: string): string {
const errorMap: Record<string, string> = {
'Invalid login credentials': 'Ungueltige Anmeldedaten',
'Email not confirmed': 'Bitte bestaetigen Sie zuerst Ihre Email',
'Too many requests': 'Zu viele Versuche. Bitte warten Sie einen Moment.',
};
return errorMap[message] || 'Ein Fehler ist aufgetreten';
}

View File

@@ -0,0 +1,110 @@
---
name: supabase-db
description: Supabase PostgreSQL Datenbank Operationen. Nutze diesen Skill fuer CRUD Operationen, SQL Migrations, Tabellen erstellen, RLS Policies, Queries und TypeScript Types generieren. Aktiviert bei Begriffen wie "Datenbank", "Tabelle erstellen", "SQL", "Query", "Migration", "RLS", "Row Level Security", "select", "insert", "update", "delete".
---
# Supabase PostgreSQL Datenbank Skill
Dieser Skill hilft bei allen Datenbank-Operationen mit Supabase.
## Tabellen erstellen
Verwende immer dieses Pattern:
```sql
CREATE TABLE table_name (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- deine Spalten hier
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- RLS aktivieren
ALTER TABLE table_name ENABLE ROW LEVEL SECURITY;
-- Updated_at Trigger
CREATE TRIGGER table_name_updated_at
BEFORE UPDATE ON table_name
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
```
## CRUD Operationen
### Select
```typescript
const { data, error } = await supabase
.from('table')
.select('*')
.eq('column', value)
.order('created_at', { ascending: false });
```
### Insert
```typescript
const { data, error } = await supabase
.from('table')
.insert({ column: value })
.select()
.single();
```
### Update
```typescript
const { data, error } = await supabase
.from('table')
.update({ column: newValue })
.eq('id', id)
.select()
.single();
```
### Delete
```typescript
const { error } = await supabase
.from('table')
.delete()
.eq('id', id);
```
## RLS Policies
```sql
-- Lesen: Jeder authentifizierte User
CREATE POLICY "Users can read own data"
ON table_name FOR SELECT
TO authenticated
USING (auth.uid() = user_id);
-- Schreiben: Nur eigene Daten
CREATE POLICY "Users can insert own data"
ON table_name FOR INSERT
TO authenticated
WITH CHECK (auth.uid() = user_id);
-- Update: Nur eigene Daten
CREATE POLICY "Users can update own data"
ON table_name FOR UPDATE
TO authenticated
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
-- Delete: Nur eigene Daten
CREATE POLICY "Users can delete own data"
ON table_name FOR DELETE
TO authenticated
USING (auth.uid() = user_id);
```
## TypeScript Types generieren
```bash
npx supabase gen types typescript --project-id PROJECT_ID > lib/database.types.ts
```
## Best Practices
1. Immer UUIDs als Primary Key
2. Immer created_at/updated_at
3. Immer RLS aktivieren
4. Soft Deletes mit deleted_at
5. Indexes fuer WHERE/ORDER BY Spalten

View File

@@ -0,0 +1,84 @@
-- Migration: {{MIGRATION_NAME}}
-- Created: {{DATE}}
-- ===========================================
-- Tabelle erstellen
-- ===========================================
CREATE TABLE IF NOT EXISTS {{TABLE_NAME}} (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Deine Spalten hier
name TEXT NOT NULL,
description TEXT,
-- Foreign Keys (optional)
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
-- Timestamps
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
);
-- ===========================================
-- Row Level Security
-- ===========================================
ALTER TABLE {{TABLE_NAME}} ENABLE ROW LEVEL SECURITY;
-- Select Policy
CREATE POLICY "{{TABLE_NAME}}_select_policy"
ON {{TABLE_NAME}} FOR SELECT
TO authenticated
USING (auth.uid() = user_id);
-- Insert Policy
CREATE POLICY "{{TABLE_NAME}}_insert_policy"
ON {{TABLE_NAME}} FOR INSERT
TO authenticated
WITH CHECK (auth.uid() = user_id);
-- Update Policy
CREATE POLICY "{{TABLE_NAME}}_update_policy"
ON {{TABLE_NAME}} FOR UPDATE
TO authenticated
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
-- Delete Policy
CREATE POLICY "{{TABLE_NAME}}_delete_policy"
ON {{TABLE_NAME}} FOR DELETE
TO authenticated
USING (auth.uid() = user_id);
-- ===========================================
-- Indexes
-- ===========================================
CREATE INDEX IF NOT EXISTS {{TABLE_NAME}}_user_id_idx ON {{TABLE_NAME}}(user_id);
CREATE INDEX IF NOT EXISTS {{TABLE_NAME}}_created_at_idx ON {{TABLE_NAME}}(created_at DESC);
-- ===========================================
-- Updated_at Trigger
-- ===========================================
-- Funktion (nur einmal pro DB noetig)
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Trigger
CREATE TRIGGER {{TABLE_NAME}}_updated_at
BEFORE UPDATE ON {{TABLE_NAME}}
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
-- ===========================================
-- Grants (optional)
-- ===========================================
-- GRANT SELECT, INSERT, UPDATE, DELETE ON {{TABLE_NAME}} TO authenticated;
-- GRANT SELECT ON {{TABLE_NAME}} TO anon;

View File

@@ -0,0 +1,142 @@
---
name: supabase-storage
description: Supabase Storage fuer Datei-Upload und Download. Nutze diesen Skill fuer Bucket-Erstellung, File Upload, Download, Signed URLs und Storage Policies. Aktiviert bei Begriffen wie "Upload", "Download", "Datei", "File", "Bild hochladen", "Storage", "Bucket", "S3", "Bilder", "Avatar", "Dokumente".
---
# Supabase Storage Skill
Dieser Skill hilft bei allen Storage-Operationen mit Supabase.
## Bucket erstellen
```typescript
// Server-side mit Admin Client
const { data, error } = await supabaseAdmin.storage.createBucket('avatars', {
public: true,
fileSizeLimit: 1024 * 1024 * 2, // 2MB
allowedMimeTypes: ['image/png', 'image/jpeg', 'image/webp']
});
```
Oder via SQL:
```sql
INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types)
VALUES (
'avatars',
'avatars',
true,
2097152,
ARRAY['image/png', 'image/jpeg', 'image/webp']
);
```
## File Upload
```typescript
async function uploadFile(file: File, bucket: string, path: string) {
const { data, error } = await supabase.storage
.from(bucket)
.upload(path, file, {
cacheControl: '3600',
upsert: false // true = ueberschreiben erlaubt
});
if (error) throw error;
// Public URL holen
const { data: urlData } = supabase.storage
.from(bucket)
.getPublicUrl(path);
return urlData.publicUrl;
}
```
## File Download
```typescript
// Als Blob
const { data, error } = await supabase.storage
.from('documents')
.download('path/to/file.pdf');
// Public URL (oeffentliche Buckets)
const { data } = supabase.storage
.from('avatars')
.getPublicUrl('user123/avatar.jpg');
// Signed URL (private Buckets, zeitlich begrenzt)
const { data, error } = await supabase.storage
.from('private-docs')
.createSignedUrl('secret.pdf', 3600); // 1 Stunde
```
## File Management
```typescript
// Liste Dateien
const { data, error } = await supabase.storage
.from('bucket')
.list('folder/', {
limit: 100,
sortBy: { column: 'created_at', order: 'desc' }
});
// Loeschen
const { error } = await supabase.storage
.from('bucket')
.remove(['path/file1.jpg', 'path/file2.jpg']);
// Verschieben
const { error } = await supabase.storage
.from('bucket')
.move('old/path.jpg', 'new/path.jpg');
```
## Storage Policies (RLS)
```sql
-- User koennen eigene Dateien hochladen
CREATE POLICY "Users 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 koennen eigene Dateien lesen
CREATE POLICY "Users read own files"
ON storage.objects FOR SELECT
TO authenticated
USING (
bucket_id = 'user-files' AND
(storage.foldername(name))[1] = auth.uid()::text
);
-- User koennen eigene Dateien loeschen
CREATE POLICY "Users delete own files"
ON storage.objects FOR DELETE
TO authenticated
USING (
bucket_id = 'user-files' AND
(storage.foldername(name))[1] = auth.uid()::text
);
-- Oeffentlicher Lesezugriff
CREATE POLICY "Public read"
ON storage.objects FOR SELECT
USING (bucket_id = 'public-assets');
```
## React Upload Component
Siehe `templates/FileUpload.tsx` fuer eine fertige Component.
## Best Practices
1. Ordnerstruktur: `{user_id}/{type}/{filename}`
2. File Size Limits setzen
3. MIME Types einschraenken
4. Signed URLs fuer private Dateien
5. Unique Filenames (z.B. mit Timestamp)

View File

@@ -0,0 +1,126 @@
'use client';
import { useState, useCallback } from 'react';
import { supabase } from '@/lib/supabase';
interface FileUploadProps {
bucket: string;
folder?: string;
accept?: string;
maxSize?: number; // in bytes
onUpload: (url: string, path: string) => void;
onError?: (error: Error) => void;
}
export function FileUpload({
bucket,
folder = 'uploads',
accept = 'image/*',
maxSize = 5 * 1024 * 1024, // 5MB default
onUpload,
onError,
}: FileUploadProps) {
const [uploading, setUploading] = useState(false);
const [progress, setProgress] = useState(0);
const [dragOver, setDragOver] = useState(false);
const uploadFile = useCallback(
async (file: File) => {
// Validierung
if (file.size > maxSize) {
onError?.(new Error(`Datei zu gross. Maximum: ${maxSize / 1024 / 1024}MB`));
return;
}
setUploading(true);
setProgress(0);
try {
// Unique filename generieren
const fileExt = file.name.split('.').pop();
const fileName = `${Date.now()}-${Math.random().toString(36).substring(2)}.${fileExt}`;
const filePath = `${folder}/${fileName}`;
// Upload
const { error: uploadError } = await supabase.storage
.from(bucket)
.upload(filePath, file, {
cacheControl: '3600',
upsert: false,
});
if (uploadError) throw uploadError;
// URL holen
const { data } = supabase.storage.from(bucket).getPublicUrl(filePath);
onUpload(data.publicUrl, filePath);
setProgress(100);
} catch (error) {
console.error('Upload error:', error);
onError?.(error instanceof Error ? error : new Error('Upload fehlgeschlagen'));
} finally {
setUploading(false);
}
},
[bucket, folder, maxSize, onUpload, onError]
);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) uploadFile(file);
};
const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
setDragOver(false);
const file = e.dataTransfer.files?.[0];
if (file) uploadFile(file);
};
return (
<div
className={`
relative border-2 border-dashed rounded-lg p-6 text-center
transition-colors cursor-pointer
${dragOver ? 'border-primary bg-primary/5' : 'border-muted-foreground/25'}
${uploading ? 'pointer-events-none opacity-50' : 'hover:border-primary'}
`}
onDragOver={(e) => {
e.preventDefault();
setDragOver(true);
}}
onDragLeave={() => setDragOver(false)}
onDrop={handleDrop}
>
<input
type="file"
accept={accept}
onChange={handleChange}
disabled={uploading}
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
/>
{uploading ? (
<div className="space-y-2">
<div className="w-full bg-muted rounded-full h-2">
<div
className="bg-primary h-2 rounded-full transition-all"
style={{ width: `${progress}%` }}
/>
</div>
<p className="text-sm text-muted-foreground">Uploading...</p>
</div>
) : (
<div className="space-y-2">
<p className="text-sm font-medium">
Datei hierher ziehen oder klicken zum Auswaehlen
</p>
<p className="text-xs text-muted-foreground">
Max. {maxSize / 1024 / 1024}MB
</p>
</div>
)}
</div>
);
}

76
.dockerignore Normal file
View File

@@ -0,0 +1,76 @@
# Dependencies
node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Testing
coverage
.nyc_output
# Next.js
.next
out
build
dist
# Misc
.DS_Store
*.pem
.AppleDouble
.LSOverride
# Debug
npm-debug.log
yarn-debug.log
yarn-error.log
# Local env files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Vercel
.vercel
# TypeScript
*.tsbuildinfo
# Git
.git
.gitignore
.gitattributes
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Documentation
README.md
CHANGELOG.md
docs/
# CI/CD
.github
.gitlab-ci.yml
.travis.yml
azure-pipelines.yml
# Docker
Dockerfile
.dockerignore
docker-compose*.yml
# Kubernetes
helm/
k8s/
*.yaml
*.yml

3
.env.example Normal file
View File

@@ -0,0 +1,3 @@
# Supabase
NEXT_PUBLIC_SUPABASE_URL=your-supabase-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key

36
.env.local.example Normal file
View File

@@ -0,0 +1,36 @@
# Application Configuration
NODE_ENV=production
PORT=3000
NEXT_TELEMETRY_DISABLED=1
# Database (Supabase or local PostgreSQL)
DATABASE_URL="postgresql://lumina:lumina_dev_password@postgres:5432/lumina_db"
# Supabase Configuration
SUPABASE_URL="https://your-project.supabase.co"
SUPABASE_ANON_KEY="your-anon-key"
SUPABASE_SERVICE_ROLE_KEY="your-service-role-key"
# Authentication
NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_SECRET="your-secret-key-here"
# Redis (Optional - for caching)
REDIS_URL="redis://redis:6379"
# API Keys
ANTHROPIC_API_KEY="your-anthropic-key"
OPENAI_API_KEY="your-openai-key"
# Email (Optional)
SMTP_HOST="smtp.example.com"
SMTP_PORT=587
SMTP_USER="your-email@example.com"
SMTP_PASSWORD="your-password"
SMTP_FROM="noreply@example.com"
# Storage (Optional)
S3_BUCKET="your-bucket"
S3_REGION="eu-central-1"
S3_ACCESS_KEY="your-access-key"
S3_SECRET_KEY="your-secret-key"

13
.eslintrc.json Normal file
View File

@@ -0,0 +1,13 @@
{
"extends": ["next/core-web-vitals", "next/typescript"],
"rules": {
"@typescript-eslint/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_"
}
],
"@typescript-eslint/no-explicit-any": "warn"
}
}

37
.gitignore vendored Normal file
View File

@@ -0,0 +1,37 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
.env
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

View File

@@ -0,0 +1,232 @@
# Semgrep Security Skill
You are a security expert using Semgrep to scan code for vulnerabilities, security issues, and best practice violations.
## Your Responsibilities
1. **Auto-Install Semgrep**: Check if Semgrep is installed, if not install it automatically
2. **Security Scanning**: Run comprehensive security scans on the codebase
3. **Vulnerability Detection**: Identify security vulnerabilities, code smells, and anti-patterns
4. **Report Generation**: Provide clear, actionable security reports
5. **Fix Suggestions**: Suggest fixes for detected issues
## Available Commands
### Check Semgrep Installation
```bash
semgrep --version
```
### Install Semgrep (if missing)
```bash
# macOS (OSX) - using Homebrew
brew install semgrep
# Linux - using apt-get
sudo apt-get update && sudo apt-get install -y semgrep
# Windows - using Chocolatey
choco install semgrep -y
# Universal fallback - using pip3 (works on all platforms)
pip3 install semgrep
```
### Run Security Scan
```bash
# Full security scan with all rules
semgrep --config=auto --json --output=semgrep-results.json .
# Quick scan with common security rules
semgrep --config=p/security-audit --json .
# OWASP Top 10 scan
semgrep --config=p/owasp-top-ten --json .
# Language-specific scans
semgrep --config=p/typescript --json .
semgrep --config=p/react --json .
semgrep --config=p/javascript --json .
```
### Scan Specific Files/Directories
```bash
# Scan specific directory
semgrep --config=auto --json app/
# Scan specific file types
semgrep --config=auto --json --include="*.ts" --include="*.tsx" .
```
## Workflow
1. **Initial Setup**
- Check if Semgrep is installed
- If not, install it automatically
- Verify installation was successful
2. **Security Scan**
- Run comprehensive scan with `--config=auto`
- Focus on high and critical severity issues first
- Scan for OWASP Top 10 vulnerabilities
3. **Analysis**
- Parse JSON results
- Categorize issues by severity (critical, high, medium, low)
- Group by vulnerability type
- Identify patterns and recurring issues
4. **Reporting**
- Summarize total issues found
- Highlight critical/high severity issues
- Provide file paths and line numbers
- Include fix suggestions
- Calculate security score
5. **Recommendations**
- Prioritize fixes (critical first)
- Suggest security best practices
- Recommend additional security measures
## Security Score Calculation
Calculate a security score based on:
- Total issues found
- Severity distribution
- Lines of code scanned
- Issue density (issues per 1000 LOC)
Formula:
```
Base Score: 100
- Critical Issue: -10 points each
- High Issue: -5 points each
- Medium Issue: -2 points each
- Low Issue: -0.5 points each
Final Score: max(0, Base Score - Total Deductions)
```
## Response Format
Always provide:
```markdown
## Security Scan Results
**Scan Date**: [timestamp]
**Files Scanned**: [count]
**Security Score**: [0-100] 🛡️
### Summary
- 🔴 Critical: [count]
- 🟠 High: [count]
- 🟡 Medium: [count]
- 🟢 Low: [count]
### Critical Issues (if any)
1. **[Vulnerability Type]** in `[file]:[line]`
- **Issue**: [description]
- **Fix**: [suggestion]
### Recommendations
- [Priority 1 action]
- [Priority 2 action]
- ...
### Next Steps
[What to do next]
```
## Important Notes
- Always run scans from the project root directory
- Use `--json` flag for machine-readable output
- Focus on actionable issues, filter out false positives
- Prioritize security issues that could lead to vulnerabilities
- Be concise but thorough in recommendations
- Update the security dashboard with latest results
## Auto-Installation Script
The Security Dashboard automatically detects your OS and installs Semgrep with the appropriate package manager:
**Automatic Installation:**
- **macOS (OSX)**: Uses Homebrew (`brew install semgrep`)
- **Linux**: Uses apt-get (`sudo apt-get install semgrep`), fallback to pip3
- **Windows**: Uses Chocolatey (`choco install semgrep`), fallback to pip3
If you want to manually install, use this script:
```bash
#!/bin/bash
echo "🔍 Checking Semgrep installation..."
if ! command -v semgrep &> /dev/null; then
echo "📦 Semgrep not found. Detecting OS and installing..."
# Detect OS
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS
echo "🍎 Detected macOS - using Homebrew"
if command -v brew &> /dev/null; then
brew install semgrep
else
echo "⚠️ Homebrew not found, using pip3 fallback"
pip3 install semgrep
fi
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
# Linux
echo "🐧 Detected Linux - using apt-get"
if command -v apt-get &> /dev/null; then
sudo apt-get update && sudo apt-get install -y semgrep
else
echo "⚠️ apt-get not found, using pip3 fallback"
pip3 install semgrep
fi
elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" || "$OSTYPE" == "win32" ]]; then
# Windows
echo "🪟 Detected Windows - using Chocolatey"
if command -v choco &> /dev/null; then
choco install semgrep -y
else
echo "⚠️ Chocolatey not found, using pip3 fallback"
pip3 install semgrep
fi
else
# Unknown OS - fallback to pip3
echo "❓ Unknown OS - using pip3 fallback"
pip3 install semgrep
fi
# Verify installation
if command -v semgrep &> /dev/null; then
echo "✅ Semgrep installed successfully!"
semgrep --version
else
echo "❌ Installation failed. Please install manually."
echo "Visit: https://semgrep.dev/docs/getting-started/"
exit 1
fi
else
echo "✅ Semgrep is already installed"
semgrep --version
fi
```
## Usage Examples
User: "Scan the code for security issues"
- Check Semgrep installation
- Run security scan with `--config=auto`
- Analyze results
- Generate report with security score
User: "Check for OWASP Top 10 vulnerabilities"
- Run scan with `--config=p/owasp-top-ten`
- Focus on critical web security issues
- Provide detailed report
User: "What's our security score?"
- Run quick scan
- Calculate security score
- Show summary dashboard

9
.prettierrc Normal file
View File

@@ -0,0 +1,9 @@
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"plugins": ["prettier-plugin-tailwindcss"]
}

76
.semgrepignore Normal file
View File

@@ -0,0 +1,76 @@
# Semgrep Ignore Configuration
# Files and directories to exclude from security scanning
# This file ensures Semgrep scans ALL subdirectories except the excluded ones
# Dependencies (CRITICAL - exclude to improve scan performance)
node_modules/
.pnp/
.pnp.js
vendor/
# Build outputs (generated files, not source code)
.next/
out/
build/
dist/
*.d.ts
.turbo/
# Test files (optional - uncomment if you want to scan tests for security)
# **/*.test.ts
# **/*.test.tsx
# **/*.spec.ts
# **/*.spec.tsx
# __tests__/
# __mocks__/
# coverage/
# Configuration files
.env*
!.env.example
# Lock files
package-lock.json
pnpm-lock.yaml
yarn.lock
# Git
.git/
.gitignore
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS files
.DS_Store
Thumbs.db
# Logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Temporary files
*.tmp
*.temp
.cache/
# Documentation (optional)
*.md
!SECURITY.md
# Prisma migrations (generated)
prisma/migrations/
# Public assets
public/
# Storybook
.storybook/
storybook-static/

269
DEPLOYMENT.md Normal file
View File

@@ -0,0 +1,269 @@
# 🚀 Deployment Guide
Quick reference for deploying your Lumina application to production.
## Table of Contents
- [Quick Start](#quick-start)
- [Docker](#docker)
- [Kubernetes](#kubernetes)
- [CI/CD](#cicd)
- [Troubleshooting](#troubleshooting)
## Quick Start
The fastest way to deploy using Make commands:
```bash
# Build and push Docker image
make docker-all
# Deploy to Kubernetes (fresh install)
make deploy-fresh
# Or upgrade existing deployment
make deploy
```
## Docker
### Build Image
```bash
# Using Makefile
make docker-build
# Or directly
docker build -t lumina-app:latest .
```
### Test Locally
```bash
# Using docker-compose (recommended)
make compose-up
# Or run container directly
docker run -p 3000:3000 --env-file .env.local lumina-app:latest
```
### Push to Harbor
```bash
# Login to Harbor
make docker-login
# Build and push
make docker-all
```
## Kubernetes
### Prerequisites
- Kubernetes cluster (1.25+)
- kubectl configured
- Helm 3.x installed
- Harbor registry access
### Deploy Application
```bash
# Full fresh deployment with secrets
make deploy-fresh
# Or step by step:
# 1. Create namespace
make k8s-namespace
# 2. Create registry secret
make k8s-secret-registry
# 3. Create app secrets (from .env.local)
make k8s-secret-app
# 4. Install with Helm
make helm-install
```
### Monitor Deployment
```bash
# View pods
make k8s-pods
# View logs
make k8s-logs
# Port forward to localhost
make k8s-port-forward
# Check Helm status
make helm-status
```
### Update Deployment
```bash
# Build new image and upgrade
IMAGE_TAG=v1.1.0 make deploy
# Or just upgrade Helm release
make helm-upgrade
```
### Customize Configuration
Edit `helm/lumina-app/values.yaml` to configure:
- **Replicas**: Number of pods
- **Resources**: CPU/Memory limits
- **Autoscaling**: Min/Max replicas
- **Ingress**: Domain settings
- **Environment**: Custom variables
- **Persistence**: Enable storage
### Rollback
```bash
# Uninstall deployment
make helm-uninstall
# Or rollback to previous version
helm rollback $(helm list -n lumina-apps -q) -n lumina-apps
```
## CI/CD
### Gitea Integration
The template supports automated deployments via Gitea Actions:
1. **Push to main** → Triggers build
2. **Build succeeds** → Push to Harbor
3. **Harbor webhook** → Update K8s deployment
### Environment Variables
Required secrets in Gitea/CI:
- `HARBOR_USERNAME`: Harbor registry username
- `HARBOR_PASSWORD`: Harbor registry password
- `KUBECONFIG`: Base64-encoded kubeconfig
### Manual Workflow
```bash
# 1. Develop locally
pnpm dev
# 2. Test with Docker Compose
make compose-up
# 3. Build and push image
make docker-all
# 4. Deploy to staging
NAMESPACE=staging make helm-install
# 5. Deploy to production
NAMESPACE=production make helm-install
```
## Troubleshooting
### Pods not starting
```bash
# Check pod status
kubectl get pods -n lumina-apps
# Describe pod for events
kubectl describe pod <pod-name> -n lumina-apps
# Check logs
kubectl logs <pod-name> -n lumina-apps
```
### Image pull errors
```bash
# Verify registry secret exists
kubectl get secret harbor-registry-secret -n lumina-apps
# Test registry access
docker login harbor.advisori.de
# Recreate secret
make k8s-secret-registry
```
### Application errors
```bash
# Check application logs
make k8s-logs
# Check environment variables
kubectl exec -it <pod-name> -n lumina-apps -- env
# Verify secrets
kubectl get secret lumina-app-secrets -n lumina-apps -o yaml
```
### Health check failures
```bash
# Test health endpoint
kubectl port-forward svc/app 3000:80 -n lumina-apps
curl http://localhost:3000/api/health
# Check probe configuration
kubectl describe pod <pod-name> -n lumina-apps | grep -A 10 "Liveness\|Readiness"
```
### Ingress issues
```bash
# Check ingress configuration
kubectl get ingress -n lumina-apps
kubectl describe ingress -n lumina-apps
# Verify cert-manager certificates
kubectl get certificates -n lumina-apps
kubectl describe certificate lumina-app-tls -n lumina-apps
```
## Useful Commands
```bash
# Show all available commands
make help
# View Helm values
helm get values $(helm list -n lumina-apps -q) -n lumina-apps
# Update single value
helm upgrade <release> ./helm/lumina-app \
--namespace lumina-apps \
--set image.tag=v1.2.0 \
--reuse-values
# Scale manually
kubectl scale deployment <release>-lumina-app --replicas=5 -n lumina-apps
# View HPA status
kubectl get hpa -n lumina-apps
kubectl describe hpa <release>-lumina-app -n lumina-apps
```
## Support
- 📚 [Documentation](https://github.com/advisori/lumina)
- 💬 [Support](mailto:info@advisori.de)
- 🐛 [Report Issues](https://github.com/advisori/lumina/issues)
---
**Generated by Lumina** - AI-Powered Development Platform

53
Dockerfile Normal file
View File

@@ -0,0 +1,53 @@
# Multi-stage build for Next.js with optimized production image
# Stage 1: Dependencies
FROM node:20-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Copy package files
COPY package*.json ./
RUN npm ci --only=production
# Stage 2: Builder
FROM node:20-alpine AS builder
WORKDIR /app
# Copy dependencies from deps stage
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Set environment variables for build
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production
# Build the application
RUN npm run build
# Stage 3: Runner
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
# Create non-root user
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Copy necessary files from builder
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
# Set correct permissions
RUN chown -R nextjs:nodejs /app
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
# Start the application
CMD ["node", "server.js"]

187
Makefile Normal file
View File

@@ -0,0 +1,187 @@
# Lumina Application Makefile
# Simplifies Docker and Kubernetes operations
# Variables
PROJECT_NAME ?= lumina-app
PROJECT_ID ?= $(shell basename $(CURDIR))
HARBOR_REGISTRY ?= harbor.advisori.de
HARBOR_PROJECT ?= lumina
IMAGE_NAME ?= $(HARBOR_REGISTRY)/$(HARBOR_PROJECT)/$(PROJECT_ID)
IMAGE_TAG ?= latest
NAMESPACE ?= lumina-apps
HELM_RELEASE ?= $(PROJECT_ID)
# Colors for output
BLUE := \033[0;34m
GREEN := \033[0;32m
YELLOW := \033[1;33m
RED := \033[0;31m
NC := \033[0m # No Color
.PHONY: help
help: ## Show this help message
@echo "$(BLUE)Lumina Application - Available Commands$(NC)"
@echo ""
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " $(GREEN)%-20s$(NC) %s\n", $$1, $$2}'
@echo ""
# Docker Commands
.PHONY: docker-build
docker-build: ## Build Docker image
@echo "$(BLUE)Building Docker image...$(NC)"
docker build -t $(IMAGE_NAME):$(IMAGE_TAG) .
@echo "$(GREEN)✓ Image built: $(IMAGE_NAME):$(IMAGE_TAG)$(NC)"
.PHONY: docker-run
docker-run: ## Run Docker container locally
@echo "$(BLUE)Starting container...$(NC)"
docker run -p 3000:3000 --env-file .env.local --name $(PROJECT_ID) $(IMAGE_NAME):$(IMAGE_TAG)
.PHONY: docker-push
docker-push: ## Push Docker image to Harbor
@echo "$(BLUE)Pushing image to Harbor...$(NC)"
docker push $(IMAGE_NAME):$(IMAGE_TAG)
@echo "$(GREEN)✓ Image pushed: $(IMAGE_NAME):$(IMAGE_TAG)$(NC)"
.PHONY: docker-login
docker-login: ## Login to Harbor registry
@echo "$(BLUE)Logging into Harbor...$(NC)"
docker login $(HARBOR_REGISTRY)
.PHONY: docker-all
docker-all: docker-build docker-push ## Build and push Docker image
@echo "$(GREEN)✓ Docker build and push complete$(NC)"
# Docker Compose Commands
.PHONY: compose-up
compose-up: ## Start services with docker-compose
@echo "$(BLUE)Starting services with docker-compose...$(NC)"
docker-compose up -d
@echo "$(GREEN)✓ Services started$(NC)"
.PHONY: compose-down
compose-down: ## Stop services with docker-compose
@echo "$(BLUE)Stopping services...$(NC)"
docker-compose down
@echo "$(GREEN)✓ Services stopped$(NC)"
.PHONY: compose-logs
compose-logs: ## View docker-compose logs
docker-compose logs -f
# Kubernetes Commands
.PHONY: k8s-namespace
k8s-namespace: ## Create Kubernetes namespace
@echo "$(BLUE)Creating namespace: $(NAMESPACE)$(NC)"
kubectl create namespace $(NAMESPACE) --dry-run=client -o yaml | kubectl apply -f -
@echo "$(GREEN)✓ Namespace ready$(NC)"
.PHONY: k8s-secret-registry
k8s-secret-registry: ## Create Harbor registry secret
@echo "$(BLUE)Creating registry secret...$(NC)"
@read -p "Harbor Username: " HARBOR_USER; \
read -sp "Harbor Password: " HARBOR_PASS; \
echo ""; \
kubectl create secret docker-registry harbor-registry-secret \
--docker-server=$(HARBOR_REGISTRY) \
--docker-username=$$HARBOR_USER \
--docker-password=$$HARBOR_PASS \
--namespace=$(NAMESPACE) \
--dry-run=client -o yaml | kubectl apply -f -
@echo "$(GREEN)✓ Registry secret created$(NC)"
.PHONY: k8s-secret-app
k8s-secret-app: ## Create application secrets from .env.local
@echo "$(BLUE)Creating application secrets...$(NC)"
@if [ ! -f .env.local ]; then \
echo "$(RED)✗ .env.local not found$(NC)"; \
exit 1; \
fi
kubectl create secret generic lumina-app-secrets \
--from-env-file=.env.local \
--namespace=$(NAMESPACE) \
--dry-run=client -o yaml | kubectl apply -f -
@echo "$(GREEN)✓ Application secrets created$(NC)"
.PHONY: helm-install
helm-install: k8s-namespace ## Install application with Helm
@echo "$(BLUE)Installing with Helm...$(NC)"
helm upgrade --install $(HELM_RELEASE) ./helm/lumina-app \
--namespace $(NAMESPACE) \
--set image.repository=$(HARBOR_REGISTRY)/$(HARBOR_PROJECT)/$(PROJECT_ID) \
--set image.tag=$(IMAGE_TAG) \
--set ingress.hosts[0].host=$(PROJECT_ID).advisori.de \
--set ingress.tls[0].hosts[0]=$(PROJECT_ID).advisori.de \
--create-namespace
@echo "$(GREEN)✓ Application deployed$(NC)"
.PHONY: helm-upgrade
helm-upgrade: ## Upgrade Helm release
@echo "$(BLUE)Upgrading Helm release...$(NC)"
helm upgrade $(HELM_RELEASE) ./helm/lumina-app \
--namespace $(NAMESPACE) \
--set image.tag=$(IMAGE_TAG) \
--reuse-values
@echo "$(GREEN)✓ Application upgraded$(NC)"
.PHONY: helm-uninstall
helm-uninstall: ## Uninstall Helm release
@echo "$(YELLOW)Uninstalling Helm release...$(NC)"
helm uninstall $(HELM_RELEASE) --namespace $(NAMESPACE)
@echo "$(GREEN)✓ Application uninstalled$(NC)"
.PHONY: helm-status
helm-status: ## Show Helm release status
helm status $(HELM_RELEASE) --namespace $(NAMESPACE)
# Kubernetes Monitoring
.PHONY: k8s-pods
k8s-pods: ## Show pods
kubectl get pods -n $(NAMESPACE) -l app.kubernetes.io/instance=$(HELM_RELEASE)
.PHONY: k8s-logs
k8s-logs: ## Show application logs
kubectl logs -f -n $(NAMESPACE) -l app.kubernetes.io/instance=$(HELM_RELEASE)
.PHONY: k8s-describe
k8s-describe: ## Describe deployment
kubectl describe deployment/$(HELM_RELEASE)-lumina-app -n $(NAMESPACE)
.PHONY: k8s-port-forward
k8s-port-forward: ## Port forward to local machine
@echo "$(BLUE)Port forwarding to localhost:3000...$(NC)"
kubectl port-forward svc/$(HELM_RELEASE)-lumina-app 3000:80 -n $(NAMESPACE)
# Full Deployment Pipeline
.PHONY: deploy
deploy: docker-all helm-upgrade ## Full deployment (build, push, upgrade)
@echo "$(GREEN)✓ Full deployment complete!$(NC)"
.PHONY: deploy-fresh
deploy-fresh: docker-all k8s-namespace k8s-secret-registry k8s-secret-app helm-install ## Fresh deployment with secrets
@echo "$(GREEN)✓ Fresh deployment complete!$(NC)"
# Cleanup
.PHONY: clean
clean: ## Clean local Docker images and containers
@echo "$(YELLOW)Cleaning up...$(NC)"
-docker rm -f $(PROJECT_ID) 2>/dev/null || true
-docker rmi $(IMAGE_NAME):$(IMAGE_TAG) 2>/dev/null || true
@echo "$(GREEN)✓ Cleanup complete$(NC)"
# Development
.PHONY: dev
dev: ## Start development server
pnpm dev
.PHONY: build
build: ## Build Next.js application
pnpm build
.PHONY: lint
lint: ## Run linter
pnpm lint
.PHONY: format
format: ## Format code
pnpm format

262
README.md Normal file
View File

@@ -0,0 +1,262 @@
# asd
ads
## Tech Stack
- **Framework:** Next.js 15 (App Router)
- **Language:** TypeScript
- **Styling:** Tailwind CSS
- **UI Components:** Spartan UI
- **Database & Auth:** Supabase
- **Code Quality:** ESLint + Prettier
## Getting Started
### Prerequisites
- Node.js 20 or higher
- pnpm 8 or higher (recommended) or npm
### Installation
```bash
# Install dependencies
pnpm install
# Copy environment variables
cp .env.example .env.local
# Update .env.local with your Supabase credentials
```
### Development
```bash
# Run development server
pnpm dev
# Open http://localhost:3000
```
### Build
```bash
# Create production build
pnpm build
# Start production server
pnpm start
```
## Project Structure
```
project-1766459980010-q6lr8s01b/
├── app/ # Next.js app router pages
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Homepage
│ └── globals.css # Global styles
├── components/ # React components
│ └── ui/ # UI components
├── lib/ # Utility functions
│ ├── supabase.ts # Supabase client
│ └── utils.ts # Helper functions
├── public/ # Static assets
└── ...config files
```
## Environment Variables
Create a `.env.local` file with:
```env
NEXT_PUBLIC_SUPABASE_URL=your-supabase-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key
```
## Scripts
- `pnpm dev` - Start development server
- `pnpm build` - Create production build
- `pnpm start` - Start production server
- `pnpm lint` - Run ESLint
- `pnpm format` - Format code with Prettier
## Deployment
This template comes with production-ready Docker and Kubernetes configurations.
### Docker Deployment
#### Build Docker Image
```bash
# Build the image
docker build -t lumina-app:latest .
# Run locally
docker run -p 3000:3000 --env-file .env.local lumina-app:latest
```
#### Docker Compose (Recommended for local testing)
```bash
# Start all services (app + postgres + redis)
docker-compose up -d
# View logs
docker-compose logs -f app
# Stop services
docker-compose down
```
#### Push to Harbor Registry
```bash
# Tag image for Harbor
docker tag lumina-app:latest harbor.advisori.de/lumina/project-1766459980010-q6lr8s01b:latest
# Login to Harbor
docker login harbor.advisori.de
# Push image
docker push harbor.advisori.de/lumina/project-1766459980010-q6lr8s01b:latest
```
### Kubernetes Deployment
#### Prerequisites
- Kubernetes cluster (1.25+)
- kubectl configured
- Helm 3.x installed
- Harbor registry access
#### Deploy with Helm
```bash
# Navigate to helm chart
cd helm/lumina-app
# Create namespace
kubectl create namespace lumina-apps
# Create 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
# Create application secrets
kubectl create secret generic lumina-app-secrets \
--from-literal=DATABASE_URL="your-database-url" \
--from-literal=SUPABASE_URL="your-supabase-url" \
--from-literal=SUPABASE_ANON_KEY="your-anon-key" \
--namespace=lumina-apps
# Install/Upgrade with Helm
helm upgrade --install project-1766459980010-q6lr8s01b . \
--namespace lumina-apps \
--set image.repository=harbor.advisori.de/lumina/project-1766459980010-q6lr8s01b \
--set image.tag=latest \
--set ingress.hosts[0].host=project-1766459980010-q6lr8s01b.advisori.de \
--set ingress.tls[0].hosts[0]=project-1766459980010-q6lr8s01b.advisori.de
```
#### Customize Deployment
Edit `helm/lumina-app/values.yaml` to customize:
- **Replicas**: Number of pods (default: 2)
- **Resources**: CPU/Memory limits
- **Autoscaling**: Min/Max replicas and thresholds
- **Ingress**: Domain and TLS settings
- **Environment**: Add custom env variables
- **Persistence**: Enable persistent storage
#### Monitor Deployment
```bash
# Check pods
kubectl get pods -n lumina-apps
# View logs
kubectl logs -f deployment/project-1766459980010-q6lr8s01b -n lumina-apps
# Check ingress
kubectl get ingress -n lumina-apps
# Port forward for testing
kubectl port-forward svc/project-1766459980010-q6lr8s01b 3000:80 -n lumina-apps
```
#### Scaling
```bash
# Manual scaling
kubectl scale deployment project-1766459980010-q6lr8s01b --replicas=5 -n lumina-apps
# Horizontal Pod Autoscaler (enabled by default)
kubectl get hpa -n lumina-apps
```
#### Update Deployment
```bash
# Build and push new image
docker build -t harbor.advisori.de/lumina/project-1766459980010-q6lr8s01b:v1.1.0 .
docker push harbor.advisori.de/lumina/project-1766459980010-q6lr8s01b:v1.1.0
# Update with new image
helm upgrade project-1766459980010-q6lr8s01b ./helm/lumina-app \
--namespace lumina-apps \
--set image.tag=v1.1.0 \
--reuse-values
# Rollback if needed
helm rollback project-1766459980010-q6lr8s01b -n lumina-apps
```
### CI/CD Integration
The template includes configurations for automated deployments:
- **Gitea Actions**: Auto-build on push to main
- **Harbor Webhook**: Trigger K8s deployment on new image
- **Helm Chart**: Version-controlled infrastructure
### Health Checks
The application exposes a health endpoint at `/api/health` for:
- Kubernetes liveness probes
- Readiness probes
- Load balancer health checks
Create `app/api/health/route.ts` if not exists:
```typescript
export async function GET() {
return Response.json({ status: 'healthy' }, { status: 200 });
}
```
## Learn More
- [Next.js Documentation](https://nextjs.org/docs)
- [Supabase Documentation](https://supabase.com/docs)
- [Spartan UI Documentation](https://www.spartan.ng/)
- [Tailwind CSS Documentation](https://tailwindcss.com/docs)
- [Kubernetes Documentation](https://kubernetes.io/docs)
- [Helm Documentation](https://helm.sh/docs)
## License
MIT
---
**Generated by Lumina** - AI-Powered Development Platform by [advisori](https://advisori.de)

238
SECURITY.md Normal file
View File

@@ -0,0 +1,238 @@
# Security
This template includes integrated security scanning powered by Semgrep.
## Security Dashboard
Access the Security Dashboard from the **Security** tab in your project:
- **Security Score**: Overall security health (0-100%)
- **Issue Breakdown**: Critical, High, Medium, and Low severity issues
- **Scan History**: View past security scans
- **Detailed Reports**: Line-by-line issue analysis with fix suggestions
## Running Security Scans
### Via Dashboard
1. Navigate to the **Security** tab
2. Click **Run Scan** button
3. Wait for analysis to complete
4. Review issues and recommendations
### Via Lumina Skill
Use the built-in Semgrep security skill:
```
User: "Scan the code for security issues"
User: "Check for OWASP Top 10 vulnerabilities"
User: "What's our security score?"
```
The skill will:
- Auto-install Semgrep if not present
- Run comprehensive security analysis
- Generate actionable reports
- Update the Security Dashboard
### Via Command Line
```bash
# Install Semgrep (if not installed) - OS-specific
# macOS (OSX)
brew install semgrep
# Linux
sudo apt-get update && sudo apt-get install -y semgrep
# Windows
choco install semgrep -y
# Universal (works on all platforms)
pip3 install semgrep
# Run security scan
semgrep --config=auto --json .
# OWASP Top 10 scan
semgrep --config=p/owasp-top-ten --json .
# Language-specific
semgrep --config=p/typescript --json .
semgrep --config=p/react --json .
```
## Security Score Calculation
Your security score is calculated based on:
```
Base Score: 100 points
Deductions:
- Critical Issue: -10 points each
- High Issue: -5 points each
- Medium Issue: -2 points each
- Low Issue: -0.5 points each
Final Score: max(0, Base Score - Total Deductions)
```
**Score Ratings:**
- 90-100: Excellent ✅
- 70-89: Good 👍
- 50-69: Fair ⚠️
- 0-49: Poor ❌
## Common Security Issues
### Critical Issues
- SQL Injection vulnerabilities
- Command Injection
- Path Traversal
- Hardcoded secrets/credentials
- Insecure cryptography
### High Issues
- XSS (Cross-Site Scripting)
- CSRF (Cross-Site Request Forgery)
- Insecure authentication
- Sensitive data exposure
- Insecure deserialization
### Medium Issues
- Missing input validation
- Weak password policies
- Insecure session management
- Missing security headers
- Information disclosure
### Low Issues
- Code smells
- Best practice violations
- Performance issues
- Deprecated functions
## Fixing Security Issues
### General Workflow
1. **Prioritize**: Fix critical and high severity issues first
2. **Review**: Understand the vulnerability and its impact
3. **Fix**: Apply recommended fixes or security patches
4. **Test**: Verify the fix doesn't break functionality
5. **Rescan**: Run a new scan to confirm the issue is resolved
### Using Lumina to Fix Issues
```
User: "Fix the SQL injection vulnerability in user-service.ts"
User: "Apply security patches for all critical issues"
User: "Review and fix the XSS issue on line 45"
```
Lumina will:
- Analyze the vulnerability
- Apply secure coding practices
- Update the code with fixes
- Run tests to verify
## Continuous Security
### Best Practices
1. **Regular Scans**: Run security scans before every deployment
2. **Pre-commit Hooks**: Add Semgrep to your git pre-commit hooks
3. **CI/CD Integration**: Include security scans in your pipeline
4. **Dependency Updates**: Keep dependencies up-to-date
5. **Security Reviews**: Conduct code reviews with security focus
### Pre-commit Hook Example
Add to `.git/hooks/pre-commit`:
```bash
#!/bin/bash
echo "Running security scan..."
semgrep --config=auto --error .
if [ $? -ne 0 ]; then
echo "Security issues found! Commit blocked."
echo "Run 'semgrep --config=auto .' to see details"
exit 1
fi
```
### CI/CD Integration
#### GitHub Actions
```yaml
name: Security Scan
on: [push, pull_request]
jobs:
semgrep:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: pip3 install semgrep
- run: semgrep --config=auto --error .
```
#### GitLab CI
```yaml
security_scan:
image: returntocorp/semgrep
script:
- semgrep --config=auto --error .
```
## Security Rules
Semgrep scans use the following rule sets:
- **auto**: Automatically curated rules for your codebase
- **p/security-audit**: General security audit rules
- **p/owasp-top-ten**: OWASP Top 10 vulnerabilities
- **p/typescript**: TypeScript-specific security rules
- **p/react**: React security best practices
- **p/javascript**: JavaScript security patterns
## False Positives
If you encounter false positives:
1. Review the finding carefully
2. Add inline comments to suppress if legitimate:
```typescript
// nosemgrep: rule-id
const result = potentiallyUnsafeOperation();
```
3. Configure `.semgrepignore` to exclude files/patterns
4. Report false positives to improve Semgrep rules
## Resources
- [Semgrep Documentation](https://semgrep.dev/docs/)
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [Semgrep Rule Registry](https://semgrep.dev/explore)
- [Security Best Practices](https://cheatsheetseries.owasp.org/)
## Support
For security concerns or questions:
- Use the Semgrep security skill in Lumina
- Check the Security Dashboard for guidance
- Review Semgrep documentation
- Consult OWASP guidelines
---
**Remember**: Security is an ongoing process, not a one-time task. Regular scans and proactive security practices keep your application safe.

27
app/api/health/route.ts Normal file
View File

@@ -0,0 +1,27 @@
/**
* Health Check Endpoint
* Used by Kubernetes liveness and readiness probes
*/
export async function GET() {
try {
// Basic health check - returns 200 if app is running
const healthData = {
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
environment: process.env.NODE_ENV || 'development',
};
return Response.json(healthData, { status: 200 });
} catch (error) {
return Response.json(
{
status: 'unhealthy',
error: error instanceof Error ? error.message : 'Unknown error',
timestamp: new Date().toISOString(),
},
{ status: 503 }
);
}
}

59
app/globals.css Normal file
View File

@@ -0,0 +1,59 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 221.2 83.2% 53.3%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 217.2 91.2% 59.8%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 224.3 76.3% 48%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

22
app/layout.tsx Normal file
View File

@@ -0,0 +1,22 @@
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import './globals.css';
const inter = Inter({ subsets: ['latin'] });
export const metadata: Metadata = {
title: 'Lumina',
description: 'Full-stack Next.js application with Supabase, Spartan UI, and Tailwind CSS',
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" suppressHydrationWarning>
<body className={inter.className}>{children}</body>
</html>
);
}

67
app/page.tsx Normal file
View File

@@ -0,0 +1,67 @@
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-center p-24">
<div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm">
<h1 className="text-4xl font-bold text-center mb-4">
Welcome to Lumina
</h1>
<p className="text-center text-muted-foreground mb-8">
Built with Next.js 15, Spartan UI, and Supabase
</p>
<div className="grid text-center lg:max-w-5xl lg:w-full lg:mb-0 lg:grid-cols-3 lg:text-left gap-4">
<a
href="https://nextjs.org/docs"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100"
target="_blank"
rel="noopener noreferrer"
>
<h2 className="mb-3 text-2xl font-semibold">
Docs{' '}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
</span>
</h2>
<p className="m-0 max-w-[30ch] text-sm opacity-50">
Find in-depth information about Next.js features and API.
</p>
</a>
<a
href="https://supabase.com/docs"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100"
target="_blank"
rel="noopener noreferrer"
>
<h2 className="mb-3 text-2xl font-semibold">
Supabase{' '}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
</span>
</h2>
<p className="m-0 max-w-[30ch] text-sm opacity-50">
Learn about Supabase authentication and database.
</p>
</a>
<a
href="https://www.spartan.ng/"
className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100"
target="_blank"
rel="noopener noreferrer"
>
<h2 className="mb-3 text-2xl font-semibold">
Spartan UI{' '}
<span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
</span>
</h2>
<p className="m-0 max-w-[30ch] text-sm opacity-50">
Explore beautiful and accessible UI components.
</p>
</a>
</div>
</div>
</main>
);
}

50
components/ui/button.tsx Normal file
View File

@@ -0,0 +1,50 @@
import * as React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
outline: 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-9 px-4 py-2',
sm: 'h-8 rounded-md px-3 text-xs',
lg: 'h-10 rounded-md px-8',
icon: 'h-9 w-9',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, ...props }, ref) => {
return (
<button
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
}
);
Button.displayName = 'Button';
export { Button, buttonVariants };

55
components/ui/card.tsx Normal file
View File

@@ -0,0 +1,55 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('rounded-xl border bg-card text-card-foreground shadow', className)}
{...props}
/>
)
);
Card.displayName = 'Card';
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} />
)
);
CardHeader.displayName = 'CardHeader';
const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn('font-semibold leading-none tracking-tight', className)}
{...props}
/>
)
);
CardTitle.displayName = 'CardTitle';
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} />
));
CardDescription.displayName = 'CardDescription';
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
)
);
CardContent.displayName = 'CardContent';
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('flex items-center p-6 pt-0', className)} {...props} />
)
);
CardFooter.displayName = 'CardFooter';
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };

79
docker-compose.yml Normal file
View File

@@ -0,0 +1,79 @@
version: '3.8'
services:
# Next.js Application
app:
build:
context: .
dockerfile: Dockerfile
container_name: lumina-app
restart: unless-stopped
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- PORT=3000
- NEXT_TELEMETRY_DISABLED=1
env_file:
- .env.local
networks:
- lumina-network
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`localhost`)"
- "traefik.http.services.app.loadbalancer.server.port=3000"
# PostgreSQL Database (Optional - for local development)
postgres:
image: postgres:16-alpine
container_name: lumina-postgres
restart: unless-stopped
ports:
- "5432:5432"
environment:
- POSTGRES_USER=lumina
- POSTGRES_PASSWORD=lumina_dev_password
- POSTGRES_DB=lumina_db
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- lumina-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U lumina"]
interval: 10s
timeout: 5s
retries: 5
# Redis Cache (Optional - for session management)
redis:
image: redis:7-alpine
container_name: lumina-redis
restart: unless-stopped
ports:
- "6379:6379"
command: redis-server --appendonly yes
volumes:
- redis-data:/data
networks:
- lumina-network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
lumina-network:
driver: bridge
volumes:
postgres-data:
driver: local
redis-data:
driver: local

View File

@@ -0,0 +1,41 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
*.swp
*.bak
*.tmp
*.orig
*~
# Helm specific
.helmignore
# CI/CD
.github/
.gitlab-ci.yml
.travis.yml
azure-pipelines.yml
# Documentation
README.md
CONTRIBUTING.md
docs/
# IDE
.idea/
.vscode/
*.iml
# Testing
test/
tests/
*.test.yaml

View File

@@ -0,0 +1,16 @@
apiVersion: v2
name: lumina-app
description: A Helm chart for Lumina Next.js applications
type: application
version: 1.0.0
appVersion: "1.0.0"
keywords:
- nextjs
- lumina
- advisori
maintainers:
- name: advisori
email: info@advisori.de
sources:
- https://github.com/advisori/lumina
icon: https://advisori.de/assets/lumina-icon.svg

View File

@@ -0,0 +1,81 @@
🚀 Lumina Application Deployed Successfully!
Your application has been deployed to Kubernetes with the following configuration:
📦 Release Name: {{ .Release.Name }}
🏷️ Namespace: {{ .Release.Namespace }}
📊 Replicas: {{ .Values.replicaCount }}
🖼️ Image: {{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}
{{- if .Values.ingress.enabled }}
🌐 Ingress Configuration:
{{- range .Values.ingress.hosts }}
🔗 https://{{ .host }}
{{- end }}
{{- else }}
⚠️ Ingress is disabled. To access the application:
kubectl port-forward svc/{{ include "lumina-app.fullname" . }} 3000:{{ .Values.service.port }} -n {{ .Release.Namespace }}
Then open http://localhost:3000 in your browser.
{{- end }}
📋 Useful Commands:
# View pods
kubectl get pods -l "app.kubernetes.io/name={{ include "lumina-app.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -n {{ .Release.Namespace }}
# View logs
kubectl logs -f deployment/{{ include "lumina-app.fullname" . }} -n {{ .Release.Namespace }}
# View service
kubectl get svc {{ include "lumina-app.fullname" . }} -n {{ .Release.Namespace }}
{{- if .Values.autoscaling.enabled }}
# View autoscaler
kubectl get hpa {{ include "lumina-app.fullname" . }} -n {{ .Release.Namespace }}
{{- end }}
# Update deployment
helm upgrade {{ .Release.Name }} . --namespace {{ .Release.Namespace }}
# Rollback deployment
helm rollback {{ .Release.Name }} -n {{ .Release.Namespace }}
{{- if .Values.persistence.enabled }}
💾 Persistence is enabled at {{ .Values.persistence.mountPath }}
{{- end }}
📊 Monitoring:
- Liveness Probe: {{ .Values.livenessProbe.httpGet.path }}
- Readiness Probe: {{ .Values.readinessProbe.httpGet.path }}
{{- if .Values.autoscaling.enabled }}
- Autoscaling: {{ .Values.autoscaling.minReplicas }}-{{ .Values.autoscaling.maxReplicas }} replicas
- Target CPU: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}%
- Target Memory: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}%
{{- end }}
🔐 Security:
- Non-root user: {{ .Values.securityContext.runAsUser }}
- Read-only filesystem: {{ .Values.securityContext.readOnlyRootFilesystem }}
✅ Next Steps:
1. Verify pods are running: kubectl get pods -n {{ .Release.Namespace }}
2. Check application logs for any errors
3. Test the health endpoint: /api/health
4. Configure monitoring and alerting
5. Set up backup strategy (if using persistence)
📚 Documentation: https://github.com/advisori/lumina
💬 Support: info@advisori.de
---
Generated by Lumina - AI-Powered Development Platform

View File

@@ -0,0 +1,60 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "lumina-app.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
*/}}
{{- define "lumina-app.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "lumina-app.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "lumina-app.labels" -}}
helm.sh/chart: {{ include "lumina-app.chart" . }}
{{ include "lumina-app.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "lumina-app.selectorLabels" -}}
app.kubernetes.io/name: {{ include "lumina-app.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "lumina-app.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "lumina-app.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,10 @@
{{- if .Values.configMap.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "lumina-app.fullname" . }}
labels:
{{- include "lumina-app.labels" . | nindent 4 }}
data:
{{- toYaml .Values.configMap.data | nindent 2 }}
{{- end }}

View File

@@ -0,0 +1,78 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "lumina-app.fullname" . }}
labels:
{{- include "lumina-app.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "lumina-app.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "lumina-app.labels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "lumina-app.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.targetPort }}
protocol: TCP
env:
{{- toYaml .Values.env | nindent 12 }}
{{- with .Values.envFrom }}
envFrom:
{{- toYaml . | nindent 12 }}
{{- end }}
livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- if .Values.persistence.enabled }}
volumeMounts:
- name: data
mountPath: {{ .Values.persistence.mountPath }}
{{- end }}
{{- if .Values.persistence.enabled }}
volumes:
- name: data
persistentVolumeClaim:
claimName: {{ include "lumina-app.fullname" . }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@@ -0,0 +1,32 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "lumina-app.fullname" . }}
labels:
{{- include "lumina-app.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "lumina-app.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,41 @@
{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "lumina-app.fullname" . }}
labels:
{{- include "lumina-app.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.className }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "lumina-app.fullname" $ }}
port:
number: {{ $.Values.service.port }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,13 @@
{{- if .Values.podDisruptionBudget.enabled }}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ include "lumina-app.fullname" . }}
labels:
{{- include "lumina-app.labels" . | nindent 4 }}
spec:
minAvailable: {{ .Values.podDisruptionBudget.minAvailable }}
selector:
matchLabels:
{{- include "lumina-app.selectorLabels" . | nindent 6 }}
{{- end }}

View File

@@ -0,0 +1,17 @@
{{- if .Values.persistence.enabled }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "lumina-app.fullname" . }}
labels:
{{- include "lumina-app.labels" . | nindent 4 }}
spec:
accessModes:
- {{ .Values.persistence.accessMode }}
{{- if .Values.persistence.storageClass }}
storageClassName: {{ .Values.persistence.storageClass }}
{{- end }}
resources:
requests:
storage: {{ .Values.persistence.size }}
{{- end }}

View File

@@ -0,0 +1,11 @@
{{- if .Values.secrets.enabled }}
apiVersion: v1
kind: Secret
metadata:
name: {{ .Values.secrets.name | default (include "lumina-app.fullname" .) }}
labels:
{{- include "lumina-app.labels" . | nindent 4 }}
type: Opaque
stringData:
{{- toYaml .Values.secrets.data | nindent 2 }}
{{- end }}

View File

@@ -0,0 +1,19 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "lumina-app.fullname" . }}
labels:
{{- include "lumina-app.labels" . | nindent 4 }}
{{- with .Values.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "lumina-app.selectorLabels" . | nindent 4 }}

View File

@@ -0,0 +1,13 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "lumina-app.serviceAccountName" . }}
labels:
{{- include "lumina-app.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
automountServiceAccountToken: {{ .Values.serviceAccount.automount }}
{{- end }}

191
helm/lumina-app/values.yaml Normal file
View File

@@ -0,0 +1,191 @@
# Default values for lumina-app
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
# Number of pod replicas
replicaCount: 2
# Container image configuration
image:
repository: harbor.advisori.de/lumina/app
pullPolicy: IfNotPresent
tag: "latest"
# Image pull secrets for private registries
imagePullSecrets:
- name: harbor-registry-secret
# Override the default name
nameOverride: ""
fullnameOverride: ""
# Service account configuration
serviceAccount:
create: true
automount: true
annotations: {}
name: ""
# Pod annotations
podAnnotations: {}
# Pod labels
podLabels:
app.kubernetes.io/component: frontend
app.kubernetes.io/part-of: lumina
# Pod security context
podSecurityContext:
runAsNonRoot: true
runAsUser: 1001
fsGroup: 1001
# Container security context
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: false
runAsNonRoot: true
runAsUser: 1001
# Service configuration
service:
type: ClusterIP
port: 80
targetPort: 3000
annotations: {}
# Ingress configuration
ingress:
enabled: true
className: "nginx"
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
hosts:
- host: app.advisori.de
paths:
- path: /
pathType: Prefix
tls:
- secretName: lumina-app-tls
hosts:
- app.advisori.de
# Resource limits and requests
resources:
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 100m
memory: 256Mi
# Horizontal Pod Autoscaler
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
targetMemoryUtilizationPercentage: 80
# Liveness probe configuration
livenessProbe:
httpGet:
path: /api/health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
successThreshold: 1
failureThreshold: 3
# Readiness probe configuration
readinessProbe:
httpGet:
path: /api/health
port: 3000
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
successThreshold: 1
failureThreshold: 3
# Node selector for pod scheduling
nodeSelector: {}
# Tolerations for pod scheduling
tolerations: []
# Affinity rules for pod scheduling
affinity: {}
# Environment variables
env:
- name: NODE_ENV
value: "production"
- name: PORT
value: "3000"
- name: NEXT_TELEMETRY_DISABLED
value: "1"
# Environment variables from secrets
envFrom:
- secretRef:
name: lumina-app-secrets
# Persistent Volume Claims (optional)
persistence:
enabled: false
storageClass: ""
accessMode: ReadWriteOnce
size: 1Gi
mountPath: /app/data
# ConfigMap for additional configuration
configMap:
enabled: false
data: {}
# Secrets (reference existing secrets)
secrets:
enabled: true
name: lumina-app-secrets
data: {}
# DATABASE_URL: ""
# SUPABASE_URL: ""
# SUPABASE_ANON_KEY: ""
# ANTHROPIC_API_KEY: ""
# Network Policy
networkPolicy:
enabled: false
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/component: ingress-nginx
egress:
- to:
- namespaceSelector: {}
ports:
- protocol: TCP
port: 443
- protocol: TCP
port: 80
# Pod Disruption Budget
podDisruptionBudget:
enabled: true
minAvailable: 1
# Service Monitor for Prometheus (optional)
serviceMonitor:
enabled: false
interval: 30s
scrapeTimeout: 10s

6
lib/supabase.ts Normal file
View File

@@ -0,0 +1,6 @@
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
export const supabase = createClient(supabaseUrl, supabaseAnonKey);

6
lib/utils.ts Normal file
View File

@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

20
next.config.js Normal file
View File

@@ -0,0 +1,20 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
output: 'standalone',
experimental: {
serverActions: {
bodySizeLimit: '2mb',
},
},
images: {
remotePatterns: [
{
protocol: 'https',
hostname: '**.supabase.co',
},
],
},
};
module.exports = nextConfig;

42
package.json Normal file
View File

@@ -0,0 +1,42 @@
{
"name": "project-1766459980010-q6lr8s01b",
"version": "0.1.0",
"description": "ads",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,css,md}\""
},
"dependencies": {
"next": "^15.1.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"@spartan-ng/ui-core": "^0.0.1-alpha.361",
"@supabase/supabase-js": "^2.47.10",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.460.0",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@types/node": "^22.10.2",
"@types/react": "^19.0.1",
"@types/react-dom": "^19.0.2",
"typescript": "^5.7.2",
"eslint": "^9.16.0",
"eslint-config-next": "^15.1.4",
"prettier": "^3.4.2",
"prettier-plugin-tailwindcss": "^0.6.9",
"tailwindcss": "^3.4.17",
"postcss": "^8.4.49",
"autoprefixer": "^10.4.20"
},
"engines": {
"node": ">=20.0.0",
"pnpm": ">=8.0.0"
}
}

4042
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

6
postcss.config.js Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

0
public/.gitkeep Normal file
View File

57
tailwind.config.ts Normal file
View File

@@ -0,0 +1,57 @@
import type { Config } from 'tailwindcss';
const config: Config = {
darkMode: ['class'],
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
colors: {
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))',
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))',
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))',
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))',
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))',
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))',
},
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))',
},
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)',
},
},
},
plugins: [require('tailwindcss-animate')],
};
export default config;

27
tsconfig.json Normal file
View File

@@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "ES2020",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}