Replace all marketing placeholder content with real MYeasyCMS content
Some checks failed
Workflow / ʦ TypeScript (push) Failing after 6m12s
Workflow / ⚫️ Test (push) Has been skipped

- 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:
Zaid Marzguioui
2026-04-01 21:09:06 +02:00
parent bbb33aa63d
commit a5bbf42901
49 changed files with 1320 additions and 4735 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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

View File

@@ -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