Replace all marketing placeholder content with real MYeasyCMS content
- Logo: Replace generic Makerkit SVG with MYeasyCMS branded logo (grid icon + styled text) - Blog: Replace 3 SaaS placeholder posts with 5 real articles (Vereinsverwaltung, SEPA, Website, DSGVO, Mitglieder-Tipps) - Changelog: Replace 6 generic entries with real feature announcements (Verbandsverwaltung, Fischerei, Dateien, Kurse, Einladungen, i18n) - Documentation: Rewrite all 20 docs from Makerkit references to MYeasyCMS content - FAQ: Replace 6 generic SaaS questions with 10 real MYeasyCMS questions - Navigation: Replace Changelog link with Contact in main nav - Footer: Reorganize into Product/Company/Legal sections - Translations: Update all EN marketing strings to match real Com.BISS content
This commit is contained in:
@@ -1,16 +1,36 @@
|
||||
---
|
||||
title: "Database"
|
||||
description: "Learn how to work with the Supabase database in your MakerKit application."
|
||||
title: "Datensicherheit"
|
||||
description: "Wie MYeasyCMS Ihre Vereinsdaten schützt — Verschlüsselung, Zugriffsrechte und Backups."
|
||||
publishedAt: 2024-04-11
|
||||
order: 2
|
||||
order: 0
|
||||
status: "published"
|
||||
---
|
||||
|
||||
MakerKit uses Supabase Postgres for database management with built-in security and performance.
|
||||
Die Sicherheit Ihrer Vereinsdaten hat für uns höchste Priorität. MYeasyCMS setzt auf mehrere Schutzebenen.
|
||||
|
||||
This section covers:
|
||||
- Database schema and structure
|
||||
- Running migrations
|
||||
- Row Level Security (RLS)
|
||||
- Querying data
|
||||
- Functions and triggers
|
||||
## Verschlüsselung
|
||||
|
||||
Alle Daten werden verschlüsselt übertragen (TLS/SSL). Die Verbindung zwischen Ihrem Browser und dem Server ist jederzeit geschützt — erkennbar am Schloss-Symbol in der Adressleiste.
|
||||
|
||||
## Server in Deutschland
|
||||
|
||||
Alle Daten werden auf Servern in deutschen Rechenzentren gespeichert. Es findet keine Datenübertragung in Drittländer statt.
|
||||
|
||||
## Zugriffsrechte
|
||||
|
||||
MYeasyCMS verwendet ein rollenbasiertes Berechtigungssystem:
|
||||
|
||||
- Jeder Benutzer sieht nur die Daten, die für seine Rolle relevant sind
|
||||
- Der Kassenwart hat Zugriff auf Finanzdaten, nicht auf Protokolle
|
||||
- Kursleiter sehen ihre Teilnehmer, nicht die Bankverbindungen
|
||||
- Nur Administratoren können Einstellungen und Benutzer verwalten
|
||||
|
||||
## Backups
|
||||
|
||||
Regelmäßige automatische Backups schützen gegen Datenverlust. Im Fall eines Problems kann der letzte Stand wiederhergestellt werden.
|
||||
|
||||
## Passwortschutz
|
||||
|
||||
- Sichere Passwortrichtlinien bei der Registrierung
|
||||
- Möglichkeit zur Multi-Faktor-Authentifizierung (MFA)
|
||||
- Automatische Sperrung bei wiederholten Fehlversuchen
|
||||
|
||||
@@ -1,446 +1,34 @@
|
||||
---
|
||||
title: "Functions & Triggers"
|
||||
description: "Create database functions and triggers for automated logic."
|
||||
title: "Automatisierungen"
|
||||
description: "Automatische Funktionen in MYeasyCMS — Beitragsberechnung, Benachrichtigungen und mehr."
|
||||
publishedAt: 2024-04-11
|
||||
order: 4
|
||||
status: "published"
|
||||
---
|
||||
|
||||
> **Note:** This is mock/placeholder content for demonstration purposes.
|
||||
MYeasyCMS automatisiert wiederkehrende Aufgaben, damit Sie sich auf die Vereinsarbeit konzentrieren können.
|
||||
|
||||
Database functions and triggers enable server-side logic and automation.
|
||||
## Beitragsberechnung
|
||||
|
||||
## Database Functions
|
||||
Beiträge werden automatisch basierend auf den zugewiesenen Beitragskategorien berechnet. Bei Eintritt oder Austritt während des Geschäftsjahres erfolgt die anteilige Berechnung.
|
||||
|
||||
### Creating a Function
|
||||
## SEPA-Erzeugung
|
||||
|
||||
```sql
|
||||
CREATE OR REPLACE FUNCTION get_user_projects(user_id UUID)
|
||||
RETURNS TABLE (
|
||||
id UUID,
|
||||
name TEXT,
|
||||
created_at TIMESTAMPTZ
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
SELECT p.id, p.name, p.created_at
|
||||
FROM projects p
|
||||
INNER JOIN accounts_memberships am ON am.account_id = p.account_id
|
||||
WHERE am.user_id = get_user_projects.user_id;
|
||||
END;
|
||||
$$;
|
||||
```
|
||||
Die SEPA-Sammellastschrift wird automatisch aus den hinterlegten Mandaten und offenen Beiträgen zusammengestellt. Sie müssen nur noch prüfen und herunterladen.
|
||||
|
||||
### Calling from TypeScript
|
||||
## E-Mail-Benachrichtigungen
|
||||
|
||||
```typescript
|
||||
const { data, error } = await client.rpc('get_user_projects', {
|
||||
user_id: userId,
|
||||
});
|
||||
```
|
||||
Das System versendet automatische Benachrichtigungen bei:
|
||||
|
||||
## Common Function Patterns
|
||||
- Neuen Kursanmeldungen
|
||||
- Eingehenden Mitgliedsanträgen
|
||||
- Fälligen Beiträgen
|
||||
- Ablaufenden SEPA-Mandaten
|
||||
|
||||
### Get User Accounts
|
||||
## Newsletter-Abonnement
|
||||
|
||||
```sql
|
||||
CREATE OR REPLACE FUNCTION get_user_accounts(user_id UUID)
|
||||
RETURNS TABLE (account_id UUID)
|
||||
LANGUAGE sql
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
SELECT account_id
|
||||
FROM accounts_memberships
|
||||
WHERE user_id = $1;
|
||||
$$;
|
||||
```
|
||||
Die An- und Abmeldung von Newslettern wird automatisch verwaltet — inklusive Double-Opt-In und Dokumentation der Einwilligung.
|
||||
|
||||
### Check Permission
|
||||
## Dokumentengenerierung
|
||||
|
||||
```sql
|
||||
CREATE OR REPLACE FUNCTION has_permission(
|
||||
user_id UUID,
|
||||
account_id UUID,
|
||||
required_role TEXT
|
||||
)
|
||||
RETURNS BOOLEAN
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
DECLARE
|
||||
user_role TEXT;
|
||||
BEGIN
|
||||
SELECT role INTO user_role
|
||||
FROM accounts_memberships
|
||||
WHERE user_id = has_permission.user_id
|
||||
AND account_id = has_permission.account_id;
|
||||
|
||||
RETURN user_role = required_role OR user_role = 'owner';
|
||||
END;
|
||||
$$;
|
||||
```
|
||||
|
||||
### Search Function
|
||||
|
||||
```sql
|
||||
CREATE OR REPLACE FUNCTION search_projects(
|
||||
search_term TEXT,
|
||||
account_id UUID
|
||||
)
|
||||
RETURNS TABLE (
|
||||
id UUID,
|
||||
name TEXT,
|
||||
description TEXT,
|
||||
relevance REAL
|
||||
)
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
SELECT
|
||||
p.id,
|
||||
p.name,
|
||||
p.description,
|
||||
ts_rank(
|
||||
to_tsvector('english', p.name || ' ' || COALESCE(p.description, '')),
|
||||
plainto_tsquery('english', search_term)
|
||||
) AS relevance
|
||||
FROM projects p
|
||||
WHERE p.account_id = search_projects.account_id
|
||||
AND (
|
||||
to_tsvector('english', p.name || ' ' || COALESCE(p.description, ''))
|
||||
@@ plainto_tsquery('english', search_term)
|
||||
)
|
||||
ORDER BY relevance DESC;
|
||||
END;
|
||||
$$;
|
||||
```
|
||||
|
||||
## Triggers
|
||||
|
||||
### Auto-Update Timestamp
|
||||
|
||||
```sql
|
||||
-- Create trigger function
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- Attach to table
|
||||
CREATE TRIGGER update_projects_updated_at
|
||||
BEFORE UPDATE ON projects
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
```
|
||||
|
||||
### Audit Log Trigger
|
||||
|
||||
```sql
|
||||
-- Create audit log table
|
||||
CREATE TABLE audit_log (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
table_name TEXT NOT NULL,
|
||||
record_id UUID NOT NULL,
|
||||
action TEXT NOT NULL,
|
||||
old_data JSONB,
|
||||
new_data JSONB,
|
||||
user_id UUID,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Create trigger function
|
||||
CREATE OR REPLACE FUNCTION log_changes()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
IF TG_OP = 'INSERT' THEN
|
||||
INSERT INTO audit_log (table_name, record_id, action, new_data, user_id)
|
||||
VALUES (TG_TABLE_NAME, NEW.id, 'INSERT', to_jsonb(NEW), auth.uid());
|
||||
RETURN NEW;
|
||||
ELSIF TG_OP = 'UPDATE' THEN
|
||||
INSERT INTO audit_log (table_name, record_id, action, old_data, new_data, user_id)
|
||||
VALUES (TG_TABLE_NAME, NEW.id, 'UPDATE', to_jsonb(OLD), to_jsonb(NEW), auth.uid());
|
||||
RETURN NEW;
|
||||
ELSIF TG_OP = 'DELETE' THEN
|
||||
INSERT INTO audit_log (table_name, record_id, action, old_data, user_id)
|
||||
VALUES (TG_TABLE_NAME, OLD.id, 'DELETE', to_jsonb(OLD), auth.uid());
|
||||
RETURN OLD;
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- Attach to table
|
||||
CREATE TRIGGER audit_projects
|
||||
AFTER INSERT OR UPDATE OR DELETE ON projects
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION log_changes();
|
||||
```
|
||||
|
||||
### Cascade Soft Delete
|
||||
|
||||
```sql
|
||||
CREATE OR REPLACE FUNCTION soft_delete_cascade()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
-- Soft delete related tasks
|
||||
UPDATE tasks
|
||||
SET deleted_at = NOW()
|
||||
WHERE project_id = OLD.id
|
||||
AND deleted_at IS NULL;
|
||||
|
||||
RETURN OLD;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER soft_delete_project_tasks
|
||||
BEFORE DELETE ON projects
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION soft_delete_cascade();
|
||||
```
|
||||
|
||||
## Validation Triggers
|
||||
|
||||
### Enforce Business Rules
|
||||
|
||||
```sql
|
||||
CREATE OR REPLACE FUNCTION validate_project_budget()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
IF NEW.budget < 0 THEN
|
||||
RAISE EXCEPTION 'Budget cannot be negative';
|
||||
END IF;
|
||||
|
||||
IF NEW.budget > 1000000 THEN
|
||||
RAISE EXCEPTION 'Budget cannot exceed 1,000,000';
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER check_project_budget
|
||||
BEFORE INSERT OR UPDATE ON projects
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION validate_project_budget();
|
||||
```
|
||||
|
||||
### Prevent Orphaned Records
|
||||
|
||||
```sql
|
||||
CREATE OR REPLACE FUNCTION prevent_owner_removal()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
owner_count INTEGER;
|
||||
BEGIN
|
||||
IF OLD.role = 'owner' THEN
|
||||
SELECT COUNT(*) INTO owner_count
|
||||
FROM accounts_memberships
|
||||
WHERE account_id = OLD.account_id
|
||||
AND role = 'owner'
|
||||
AND id != OLD.id;
|
||||
|
||||
IF owner_count = 0 THEN
|
||||
RAISE EXCEPTION 'Cannot remove the last owner of an account';
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
RETURN OLD;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER check_owner_before_delete
|
||||
BEFORE DELETE ON accounts_memberships
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION prevent_owner_removal();
|
||||
```
|
||||
|
||||
## Computed Columns
|
||||
|
||||
### Virtual Column with Function
|
||||
|
||||
```sql
|
||||
CREATE OR REPLACE FUNCTION project_task_count(project_id UUID)
|
||||
RETURNS INTEGER
|
||||
LANGUAGE sql
|
||||
STABLE
|
||||
AS $$
|
||||
SELECT COUNT(*)::INTEGER
|
||||
FROM tasks
|
||||
WHERE project_id = $1
|
||||
AND deleted_at IS NULL;
|
||||
$$;
|
||||
|
||||
-- Use in queries
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
project_task_count(id) as task_count
|
||||
FROM projects;
|
||||
```
|
||||
|
||||
## Event Notifications
|
||||
|
||||
### Notify on Changes
|
||||
|
||||
```sql
|
||||
CREATE OR REPLACE FUNCTION notify_project_change()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
PERFORM pg_notify(
|
||||
'project_changes',
|
||||
json_build_object(
|
||||
'operation', TG_OP,
|
||||
'record', NEW
|
||||
)::text
|
||||
);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER project_change_notification
|
||||
AFTER INSERT OR UPDATE ON projects
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION notify_project_change();
|
||||
```
|
||||
|
||||
### Listen in TypeScript
|
||||
|
||||
```typescript
|
||||
const channel = client
|
||||
.channel('project_changes')
|
||||
.on(
|
||||
'postgres_changes',
|
||||
{
|
||||
event: '*',
|
||||
schema: 'public',
|
||||
table: 'projects',
|
||||
},
|
||||
(payload) => {
|
||||
console.log('Project changed:', payload);
|
||||
}
|
||||
)
|
||||
.subscribe();
|
||||
```
|
||||
|
||||
## Security Functions
|
||||
|
||||
### Row Level Security Helper
|
||||
|
||||
```sql
|
||||
CREATE OR REPLACE FUNCTION is_account_member(account_id UUID)
|
||||
RETURNS BOOLEAN
|
||||
LANGUAGE sql
|
||||
SECURITY DEFINER
|
||||
STABLE
|
||||
AS $$
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM accounts_memberships
|
||||
WHERE account_id = $1
|
||||
AND user_id = auth.uid()
|
||||
);
|
||||
$$;
|
||||
|
||||
-- Use in RLS policy
|
||||
CREATE POLICY "Users can access their account's projects"
|
||||
ON projects FOR ALL
|
||||
USING (is_account_member(account_id));
|
||||
```
|
||||
|
||||
## Scheduled Functions
|
||||
|
||||
### Using pg_cron Extension
|
||||
|
||||
```sql
|
||||
-- Enable pg_cron extension
|
||||
CREATE EXTENSION IF NOT EXISTS pg_cron;
|
||||
|
||||
-- Schedule cleanup job
|
||||
SELECT cron.schedule(
|
||||
'cleanup-old-sessions',
|
||||
'0 2 * * *', -- Every day at 2 AM
|
||||
$$
|
||||
DELETE FROM sessions
|
||||
WHERE expires_at < NOW();
|
||||
$$
|
||||
);
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use SECURITY DEFINER carefully** - Can bypass RLS
|
||||
2. **Add error handling** - Use EXCEPTION blocks
|
||||
3. **Keep functions simple** - One responsibility per function
|
||||
4. **Document functions** - Add comments
|
||||
5. **Test thoroughly** - Unit test database functions
|
||||
6. **Use STABLE/IMMUTABLE** - Performance optimization
|
||||
7. **Avoid side effects** - Make functions predictable
|
||||
8. **Return proper types** - Use RETURNS TABLE for clarity
|
||||
|
||||
## Testing Functions
|
||||
|
||||
```sql
|
||||
-- Test function
|
||||
DO $$
|
||||
DECLARE
|
||||
result INTEGER;
|
||||
BEGIN
|
||||
SELECT project_task_count('some-uuid') INTO result;
|
||||
|
||||
ASSERT result >= 0, 'Task count should not be negative';
|
||||
|
||||
RAISE NOTICE 'Test passed: task count = %', result;
|
||||
END;
|
||||
$$;
|
||||
```
|
||||
|
||||
## Debugging
|
||||
|
||||
### Enable Function Logging
|
||||
|
||||
```sql
|
||||
CREATE OR REPLACE FUNCTION debug_function()
|
||||
RETURNS void
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
RAISE NOTICE 'Debug: Processing started';
|
||||
RAISE NOTICE 'Debug: Current user is %', auth.uid();
|
||||
-- Your function logic
|
||||
RAISE NOTICE 'Debug: Processing completed';
|
||||
END;
|
||||
$$;
|
||||
```
|
||||
|
||||
### Check Function Execution
|
||||
|
||||
```sql
|
||||
-- View function execution stats
|
||||
SELECT
|
||||
schemaname,
|
||||
funcname,
|
||||
calls,
|
||||
total_time,
|
||||
self_time
|
||||
FROM pg_stat_user_functions
|
||||
ORDER BY total_time DESC;
|
||||
```
|
||||
Mitgliedsausweise, Rechnungen und Serienbriefe werden automatisch aus Vorlagen generiert. Platzhalter werden mit den aktuellen Mitgliederdaten befüllt.
|
||||
|
||||
@@ -1,68 +1,27 @@
|
||||
---
|
||||
title: "Migrations"
|
||||
description: "Learn how to create and manage database migrations in your application."
|
||||
title: "Updates und Änderungen"
|
||||
description: "Wie MYeasyCMS aktualisiert wird — automatische Updates ohne Aufwand für den Verein."
|
||||
publishedAt: 2024-04-11
|
||||
order: 1
|
||||
status: "published"
|
||||
---
|
||||
|
||||
> **Note:** This is mock/placeholder content for demonstration purposes.
|
||||
MYeasyCMS wird kontinuierlich weiterentwickelt. Neue Funktionen und Verbesserungen stehen automatisch zur Verfügung.
|
||||
|
||||
Database migrations allow you to version control your database schema changes and apply them consistently across environments.
|
||||
## Automatische Updates
|
||||
|
||||
## Creating a Migration
|
||||
Als webbasierte Plattform wird MYeasyCMS zentral aktualisiert. Sie müssen:
|
||||
|
||||
To create a new migration, use the following command:
|
||||
- Keine Software herunterladen
|
||||
- Keine Installation durchführen
|
||||
- Keine Datenbank-Updates ausführen
|
||||
|
||||
```bash
|
||||
pnpm --filter web supabase:db:diff
|
||||
```
|
||||
Alle Änderungen werden vom Com.BISS-Team eingespielt und getestet, bevor sie live gehen.
|
||||
|
||||
This will generate a new migration file in the `apps/web/supabase/migrations` directory based on the differences between your local database and the schema files.
|
||||
## Änderungsprotokoll
|
||||
|
||||
## Applying Migrations
|
||||
Unter **Changelog** auf der Website informieren wir über neue Funktionen, Verbesserungen und Fehlerbehebungen. Bei größeren Änderungen werden Sie zusätzlich per E-Mail informiert.
|
||||
|
||||
To apply migrations to your local database:
|
||||
## Feedback und Wünsche
|
||||
|
||||
```bash
|
||||
pnpm --filter web supabase migrations up
|
||||
```
|
||||
|
||||
## Migration Best Practices
|
||||
|
||||
1. **Always test migrations locally first** before applying to production
|
||||
2. **Make migrations reversible** when possible by including DOWN statements
|
||||
3. **Use transactions** to ensure atomic operations
|
||||
4. **Add indexes** for foreign keys and frequently queried columns
|
||||
5. **Include RLS policies** in the same migration as table creation
|
||||
|
||||
## Example Migration
|
||||
|
||||
```sql
|
||||
-- Create a new table
|
||||
CREATE TABLE tasks (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
account_id UUID NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
|
||||
title TEXT NOT NULL,
|
||||
completed BOOLEAN DEFAULT false,
|
||||
created_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- Add RLS
|
||||
ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Create policies
|
||||
CREATE POLICY "Users can view their account tasks"
|
||||
ON tasks FOR SELECT
|
||||
USING (account_id IN (SELECT get_user_accounts(auth.uid())));
|
||||
```
|
||||
|
||||
## Resetting the Database
|
||||
|
||||
To completely reset your local database with the latest schema:
|
||||
|
||||
```bash
|
||||
pnpm supabase:web:reset
|
||||
```
|
||||
|
||||
This will drop all tables and reapply all migrations from scratch.
|
||||
Haben Sie Ideen für neue Funktionen oder Verbesserungen? Sprechen Sie uns an — viele Funktionen in MYeasyCMS sind auf Wunsch unserer Kunden entstanden. Neuentwicklungen zu festen Preisen, keine Überraschungen.
|
||||
|
||||
@@ -1,430 +1,34 @@
|
||||
---
|
||||
title: "Querying Data"
|
||||
description: "Learn how to query and filter data from your database."
|
||||
title: "Datenexport"
|
||||
description: "Exportieren Sie Ihre Vereinsdaten in verschiedenen Formaten — für Berichte, Auswertungen und Datensicherung."
|
||||
publishedAt: 2024-04-11
|
||||
order: 3
|
||||
order: 1
|
||||
status: "published"
|
||||
---
|
||||
|
||||
> **Note:** This is mock/placeholder content for demonstration purposes.
|
||||
MYeasyCMS bietet umfangreiche Exportmöglichkeiten für alle Ihre Vereinsdaten.
|
||||
|
||||
Efficiently query and filter data using Supabase's query builder.
|
||||
## Exportformate
|
||||
|
||||
## Basic Queries
|
||||
- **Excel (.xlsx)** — Für die Weiterverarbeitung in Tabellenkalkulationen
|
||||
- **CSV** — Universelles Format für den Datenaustausch
|
||||
- **PDF** — Für druckfertige Berichte und Dokumente
|
||||
- **SEPA XML (pain.008)** — Für den Bankeinzug per Lastschrift
|
||||
|
||||
### Select All
|
||||
## Was kann exportiert werden?
|
||||
|
||||
```typescript
|
||||
const { data, error } = await client
|
||||
.from('projects')
|
||||
.select('*');
|
||||
```
|
||||
### Mitgliederdaten
|
||||
Exportieren Sie die vollständige Mitgliederliste oder gefilterte Teilmengen — nach Abteilung, Status, Eintrittsdatum oder anderen Kriterien.
|
||||
|
||||
### Select Specific Columns
|
||||
### Finanzdaten
|
||||
Beitragsübersichten, offene Posten, Zahlungshistorie und SEPA-Dateien für den Bankeinzug.
|
||||
|
||||
```typescript
|
||||
const { data, error } = await client
|
||||
.from('projects')
|
||||
.select('id, name, created_at');
|
||||
```
|
||||
### Kurs- und Veranstaltungsdaten
|
||||
Teilnehmerlisten, Kursübersichten und Anwesenheitslisten.
|
||||
|
||||
### Select with Related Data
|
||||
### Dokumente
|
||||
Generierte Dokumente (Ausweise, Rechnungen, Briefe) als PDF herunterladen.
|
||||
|
||||
```typescript
|
||||
const { data, error } = await client
|
||||
.from('projects')
|
||||
.select(`
|
||||
id,
|
||||
name,
|
||||
account:accounts(id, name),
|
||||
tasks(id, title, completed)
|
||||
`);
|
||||
```
|
||||
## Datensicherung
|
||||
|
||||
## Filtering
|
||||
|
||||
### Equal
|
||||
|
||||
```typescript
|
||||
const { data } = await client
|
||||
.from('projects')
|
||||
.select('*')
|
||||
.eq('status', 'active');
|
||||
```
|
||||
|
||||
### Not Equal
|
||||
|
||||
```typescript
|
||||
const { data } = await client
|
||||
.from('projects')
|
||||
.select('*')
|
||||
.neq('status', 'deleted');
|
||||
```
|
||||
|
||||
### Greater Than / Less Than
|
||||
|
||||
```typescript
|
||||
const { data } = await client
|
||||
.from('projects')
|
||||
.select('*')
|
||||
.gt('created_at', '2024-01-01')
|
||||
.lt('budget', 10000);
|
||||
```
|
||||
|
||||
### In Array
|
||||
|
||||
```typescript
|
||||
const { data } = await client
|
||||
.from('projects')
|
||||
.select('*')
|
||||
.in('status', ['active', 'pending']);
|
||||
```
|
||||
|
||||
### Like (Pattern Matching)
|
||||
|
||||
```typescript
|
||||
const { data } = await client
|
||||
.from('projects')
|
||||
.select('*')
|
||||
.like('name', '%website%');
|
||||
```
|
||||
|
||||
### Full-Text Search
|
||||
|
||||
```typescript
|
||||
const { data } = await client
|
||||
.from('projects')
|
||||
.select('*')
|
||||
.textSearch('description', 'design & development');
|
||||
```
|
||||
|
||||
## Ordering
|
||||
|
||||
### Order By
|
||||
|
||||
```typescript
|
||||
const { data } = await client
|
||||
.from('projects')
|
||||
.select('*')
|
||||
.order('created_at', { ascending: false });
|
||||
```
|
||||
|
||||
### Multiple Order By
|
||||
|
||||
```typescript
|
||||
const { data } = await client
|
||||
.from('projects')
|
||||
.select('*')
|
||||
.order('status')
|
||||
.order('created_at', { ascending: false });
|
||||
```
|
||||
|
||||
## Pagination
|
||||
|
||||
### Limit
|
||||
|
||||
```typescript
|
||||
const { data } = await client
|
||||
.from('projects')
|
||||
.select('*')
|
||||
.limit(10);
|
||||
```
|
||||
|
||||
### Range (Offset)
|
||||
|
||||
```typescript
|
||||
const page = 2;
|
||||
const pageSize = 10;
|
||||
const from = (page - 1) * pageSize;
|
||||
const to = from + pageSize - 1;
|
||||
|
||||
const { data, count } = await client
|
||||
.from('projects')
|
||||
.select('*', { count: 'exact' })
|
||||
.range(from, to);
|
||||
```
|
||||
|
||||
## Aggregations
|
||||
|
||||
### Count
|
||||
|
||||
```typescript
|
||||
const { count } = await client
|
||||
.from('projects')
|
||||
.select('*', { count: 'exact', head: true });
|
||||
```
|
||||
|
||||
### Count with Filters
|
||||
|
||||
```typescript
|
||||
const { count } = await client
|
||||
.from('projects')
|
||||
.select('*', { count: 'exact', head: true })
|
||||
.eq('status', 'active');
|
||||
```
|
||||
|
||||
## Advanced Queries
|
||||
|
||||
### Multiple Filters
|
||||
|
||||
```typescript
|
||||
const { data } = await client
|
||||
.from('projects')
|
||||
.select('*')
|
||||
.eq('account_id', accountId)
|
||||
.eq('status', 'active')
|
||||
.gte('created_at', startDate)
|
||||
.lte('created_at', endDate)
|
||||
.order('created_at', { ascending: false })
|
||||
.limit(20);
|
||||
```
|
||||
|
||||
### OR Conditions
|
||||
|
||||
```typescript
|
||||
const { data } = await client
|
||||
.from('projects')
|
||||
.select('*')
|
||||
.or('status.eq.active,status.eq.pending');
|
||||
```
|
||||
|
||||
### Nested OR
|
||||
|
||||
```typescript
|
||||
const { data } = await client
|
||||
.from('projects')
|
||||
.select('*')
|
||||
.or('and(status.eq.active,priority.eq.high),status.eq.urgent');
|
||||
```
|
||||
|
||||
## Joins
|
||||
|
||||
### Inner Join
|
||||
|
||||
```typescript
|
||||
const { data } = await client
|
||||
.from('projects')
|
||||
.select(`
|
||||
*,
|
||||
account:accounts!inner(
|
||||
id,
|
||||
name
|
||||
)
|
||||
`)
|
||||
.eq('account.name', 'Acme Corp');
|
||||
```
|
||||
|
||||
### Left Join
|
||||
|
||||
```typescript
|
||||
const { data } = await client
|
||||
.from('projects')
|
||||
.select(`
|
||||
*,
|
||||
tasks(*)
|
||||
`);
|
||||
```
|
||||
|
||||
## Null Handling
|
||||
|
||||
### Is Null
|
||||
|
||||
```typescript
|
||||
const { data } = await client
|
||||
.from('projects')
|
||||
.select('*')
|
||||
.is('completed_at', null);
|
||||
```
|
||||
|
||||
### Not Null
|
||||
|
||||
```typescript
|
||||
const { data} = await client
|
||||
.from('projects')
|
||||
.select('*')
|
||||
.not('completed_at', 'is', null);
|
||||
```
|
||||
|
||||
## Insert Data
|
||||
|
||||
### Single Insert
|
||||
|
||||
```typescript
|
||||
const { data, error } = await client
|
||||
.from('projects')
|
||||
.insert({
|
||||
name: 'New Project',
|
||||
account_id: accountId,
|
||||
status: 'active',
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
```
|
||||
|
||||
### Multiple Insert
|
||||
|
||||
```typescript
|
||||
const { data, error } = await client
|
||||
.from('projects')
|
||||
.insert([
|
||||
{ name: 'Project 1', account_id: accountId },
|
||||
{ name: 'Project 2', account_id: accountId },
|
||||
])
|
||||
.select();
|
||||
```
|
||||
|
||||
## Update Data
|
||||
|
||||
### Update with Filter
|
||||
|
||||
```typescript
|
||||
const { data, error } = await client
|
||||
.from('projects')
|
||||
.update({ status: 'completed' })
|
||||
.eq('id', projectId)
|
||||
.select()
|
||||
.single();
|
||||
```
|
||||
|
||||
### Update Multiple Rows
|
||||
|
||||
```typescript
|
||||
const { data, error } = await client
|
||||
.from('projects')
|
||||
.update({ status: 'archived' })
|
||||
.eq('account_id', accountId)
|
||||
.lt('updated_at', oldDate);
|
||||
```
|
||||
|
||||
## Delete Data
|
||||
|
||||
### Delete with Filter
|
||||
|
||||
```typescript
|
||||
const { error } = await client
|
||||
.from('projects')
|
||||
.delete()
|
||||
.eq('id', projectId);
|
||||
```
|
||||
|
||||
### Delete Multiple
|
||||
|
||||
```typescript
|
||||
const { error } = await client
|
||||
.from('projects')
|
||||
.delete()
|
||||
.in('id', projectIds);
|
||||
```
|
||||
|
||||
## Upsert
|
||||
|
||||
### Insert or Update
|
||||
|
||||
```typescript
|
||||
const { data, error } = await client
|
||||
.from('projects')
|
||||
.upsert({
|
||||
id: projectId,
|
||||
name: 'Updated Name',
|
||||
status: 'active',
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
```
|
||||
|
||||
## RPC (Stored Procedures)
|
||||
|
||||
### Call Database Function
|
||||
|
||||
```typescript
|
||||
const { data, error } = await client
|
||||
.rpc('get_user_projects', {
|
||||
user_id: userId,
|
||||
});
|
||||
```
|
||||
|
||||
### With Complex Parameters
|
||||
|
||||
```typescript
|
||||
const { data, error } = await client
|
||||
.rpc('search_projects', {
|
||||
search_term: 'design',
|
||||
account_ids: [1, 2, 3],
|
||||
min_budget: 5000,
|
||||
});
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Basic Error Handling
|
||||
|
||||
```typescript
|
||||
const { data, error } = await client
|
||||
.from('projects')
|
||||
.select('*');
|
||||
|
||||
if (error) {
|
||||
console.error('Error fetching projects:', error.message);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data;
|
||||
```
|
||||
|
||||
### Typed Error Handling
|
||||
|
||||
```typescript
|
||||
import { PostgrestError } from '@supabase/supabase-js';
|
||||
|
||||
function handleDatabaseError(error: PostgrestError) {
|
||||
switch (error.code) {
|
||||
case '23505': // unique_violation
|
||||
throw new Error('A project with this name already exists');
|
||||
case '23503': // foreign_key_violation
|
||||
throw new Error('Invalid account reference');
|
||||
default:
|
||||
throw new Error('Database error: ' + error.message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## TypeScript Types
|
||||
|
||||
### Generated Types
|
||||
|
||||
```typescript
|
||||
import { Database } from '~/types/database.types';
|
||||
|
||||
type Project = Database['public']['Tables']['projects']['Row'];
|
||||
type ProjectInsert = Database['public']['Tables']['projects']['Insert'];
|
||||
type ProjectUpdate = Database['public']['Tables']['projects']['Update'];
|
||||
```
|
||||
|
||||
### Typed Queries
|
||||
|
||||
```typescript
|
||||
const { data } = await client
|
||||
.from('projects')
|
||||
.select('*')
|
||||
.returns<Project[]>();
|
||||
```
|
||||
|
||||
## Performance Tips
|
||||
|
||||
1. **Select only needed columns** - Don't use `select('*')` unnecessarily
|
||||
2. **Use indexes** - Create indexes on frequently filtered columns
|
||||
3. **Limit results** - Always paginate large datasets
|
||||
4. **Avoid N+1 queries** - Use joins instead of multiple queries
|
||||
5. **Use RPC for complex queries** - Move logic to database
|
||||
6. **Cache when possible** - Use React Query or similar
|
||||
7. **Profile queries** - Use `EXPLAIN ANALYZE` in SQL
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always handle errors** - Check error responses
|
||||
2. **Validate input** - Use Zod or similar
|
||||
3. **Use TypeScript** - Generate and use types
|
||||
4. **Consistent naming** - Follow database naming conventions
|
||||
5. **Document complex queries** - Add comments
|
||||
6. **Test queries** - Unit test database operations
|
||||
7. **Monitor performance** - Track slow queries
|
||||
Für die eigene Datensicherung können Sie regelmäßig einen Gesamtexport Ihrer Mitgliederdaten erstellen. Dies dient als zusätzliche Sicherheit neben den automatischen Server-Backups.
|
||||
|
||||
@@ -1,88 +1,41 @@
|
||||
---
|
||||
title: "Row Level Security"
|
||||
description: "Understanding and implementing Row Level Security (RLS) for data protection."
|
||||
title: "Zugriffsrechte"
|
||||
description: "Rollenbasierte Zugriffsrechte — wer welche Daten sehen und bearbeiten darf."
|
||||
publishedAt: 2024-04-11
|
||||
order: 2
|
||||
status: "published"
|
||||
---
|
||||
|
||||
> **Note:** This is mock/placeholder content for demonstration purposes.
|
||||
MYeasyCMS schützt Ihre Daten durch ein feingranulares Berechtigungssystem.
|
||||
|
||||
Row Level Security (RLS) is PostgreSQL's built-in authorization system that controls which rows users can access in database tables.
|
||||
## Rollenkonzept
|
||||
|
||||
## Why RLS?
|
||||
Jeder Benutzer erhält eine Rolle, die seinen Zugriff auf Module und Daten steuert. Das Prinzip: Jeder sieht nur das, was er für seine Aufgabe braucht.
|
||||
|
||||
RLS provides several advantages:
|
||||
- **Database-level security** - Protection even if application code has bugs
|
||||
- **Automatic enforcement** - No need for manual authorization checks
|
||||
- **Multi-tenant isolation** - Ensures users only see their own data
|
||||
- **Performance** - Optimized at the database level
|
||||
## Standardrollen
|
||||
|
||||
## Enabling RLS
|
||||
| Rolle | Zugriff |
|
||||
|-------|---------|
|
||||
| **Administrator** | Alle Module, Einstellungen, Benutzerverwaltung |
|
||||
| **Kassenwart** | Finanzen, Beiträge, Mitgliederdaten, SEPA |
|
||||
| **Kursleiter** | Kurse, Teilnehmer, Anwesenheit |
|
||||
| **Schriftführer** | Protokolle, Dokumente |
|
||||
| **Mitglied** | Eigene Daten über das Portal |
|
||||
|
||||
All tables should have RLS enabled:
|
||||
## Datenebene
|
||||
|
||||
```sql
|
||||
ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;
|
||||
```
|
||||
Die Zugriffsrechte wirken auf Datenebene:
|
||||
|
||||
## Common Policy Patterns
|
||||
- Ein Kursleiter sieht nur die Teilnehmer seiner Kurse
|
||||
- Ein Mitglied sieht nur seine eigenen Daten im Portal
|
||||
- SEPA-Mandate und Bankverbindungen sind nur für den Kassenwart und Administratoren sichtbar
|
||||
|
||||
### Personal Account Access
|
||||
## Administratorrechte
|
||||
|
||||
```sql
|
||||
CREATE POLICY "Users can access their personal account data"
|
||||
ON your_table FOR ALL
|
||||
USING (account_id = auth.uid());
|
||||
```
|
||||
Nur Administratoren können:
|
||||
|
||||
### Team Account Access
|
||||
|
||||
```sql
|
||||
CREATE POLICY "Users can access their team account data"
|
||||
ON your_table FOR ALL
|
||||
USING (
|
||||
account_id IN (
|
||||
SELECT account_id FROM accounts_memberships
|
||||
WHERE user_id = auth.uid()
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
### Read vs Write Permissions
|
||||
|
||||
```sql
|
||||
-- All members can read
|
||||
CREATE POLICY "Team members can view data"
|
||||
ON your_table FOR SELECT
|
||||
USING (account_id IN (SELECT get_user_accounts(auth.uid())));
|
||||
|
||||
-- Only owners can modify
|
||||
CREATE POLICY "Only owners can modify data"
|
||||
ON your_table FOR UPDATE
|
||||
USING (
|
||||
account_id IN (
|
||||
SELECT account_id FROM accounts_memberships
|
||||
WHERE user_id = auth.uid() AND role = 'owner'
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
## Testing RLS Policies
|
||||
|
||||
Always test your RLS policies to ensure they work correctly:
|
||||
|
||||
```sql
|
||||
-- Test as specific user
|
||||
SET request.jwt.claims.sub = 'user-uuid-here';
|
||||
|
||||
-- Try to select data
|
||||
SELECT * FROM your_table;
|
||||
|
||||
-- Reset
|
||||
RESET request.jwt.claims.sub;
|
||||
```
|
||||
|
||||
## Admin Bypass
|
||||
|
||||
Service role keys bypass RLS. Use with extreme caution and always implement manual authorization checks when using the admin client.
|
||||
- Neue Benutzer einladen
|
||||
- Rollen zuweisen
|
||||
- Module aktivieren/deaktivieren
|
||||
- Vereinseinstellungen ändern
|
||||
- Das Audit-Protokoll einsehen
|
||||
|
||||
@@ -1,43 +1,38 @@
|
||||
---
|
||||
title: "Database Overview"
|
||||
description: "Understanding the database schema and table structure in your application."
|
||||
title: "Datenstruktur"
|
||||
description: "Wie MYeasyCMS Ihre Vereinsdaten organisiert — Stammdaten, Module und Verknüpfungen."
|
||||
publishedAt: 2024-04-11
|
||||
order: 0
|
||||
status: "published"
|
||||
---
|
||||
|
||||
> **Note:** This is mock/placeholder content for demonstration purposes.
|
||||
MYeasyCMS organisiert Ihre Vereinsdaten in einer durchdachten Struktur.
|
||||
|
||||
The database schema is designed with a multi-tenant architecture that supports both personal and team accounts.
|
||||
## Mitgliederstamm
|
||||
|
||||
## Core Tables
|
||||
Im Zentrum steht der Mitgliederstamm. Jedes Mitglied hat:
|
||||
|
||||
### Users Table
|
||||
The `users` table stores user authentication data and is managed by Supabase Auth:
|
||||
- `id` - Unique user identifier
|
||||
- `email` - User's email address
|
||||
- `created_at` - Account creation timestamp
|
||||
- **Persönliche Daten** — Name, Geburtsdatum, Geschlecht
|
||||
- **Kontaktdaten** — Adresse, Telefon, E-Mail
|
||||
- **Vereinsdaten** — Eintrittsdatum, Mitgliedsnummer, Status
|
||||
- **Abteilungen** — Zugehörigkeit zu einer oder mehreren Abteilungen
|
||||
- **SEPA-Mandat** — Bankverbindung und Mandatsdaten
|
||||
- **Notizen** — Freitextfeld für Anmerkungen
|
||||
|
||||
### Accounts Table
|
||||
The `accounts` table represents both personal and team accounts:
|
||||
- `id` - Unique account identifier
|
||||
- `name` - Account display name
|
||||
- `slug` - URL-friendly identifier
|
||||
- `is_personal_account` - Boolean flag for personal vs team accounts
|
||||
## Verknüpfungen
|
||||
|
||||
### Projects Table
|
||||
Store your application's project data:
|
||||
- `id` - Unique project identifier
|
||||
- `account_id` - Foreign key to accounts table
|
||||
- `name` - Project name
|
||||
- `description` - Project description
|
||||
- `created_at` - Creation timestamp
|
||||
Mitgliederdaten sind mit anderen Modulen verknüpft:
|
||||
|
||||
## Relationships
|
||||
- **Beiträge** — Welche Beiträge sind fällig, bezahlt oder offen?
|
||||
- **Kurse** — An welchen Kursen nimmt das Mitglied teil?
|
||||
- **Veranstaltungen** — Für welche Events ist es angemeldet?
|
||||
- **Dokumente** — Welche Ausweise und Rechnungen wurden generiert?
|
||||
|
||||
All data in the application is tied to accounts through foreign key relationships. This ensures proper data isolation and access control through Row Level Security (RLS).
|
||||
## Individuelle Module
|
||||
|
||||
## Next Steps
|
||||
Mit dem Modul-Baukasten können Sie eigene Datenmodule erstellen:
|
||||
|
||||
- Learn about [migrations](/docs/database/migrations)
|
||||
- Understand [RLS policies](/docs/database/row-level-security)
|
||||
- **Felder definieren** — Text, Zahl, Datum, Auswahl, Dateiupload und mehr
|
||||
- **Formulare gestalten** — Layout und Reihenfolge der Felder festlegen
|
||||
- **Beziehungen** — Module miteinander verknüpfen
|
||||
- **Import/Export** — Daten aus Excel oder CSV importieren
|
||||
|
||||
Reference in New Issue
Block a user