feat: Standard-Genehmiger-Rollen automatisch bei Firmenanlage anlegen
Jede neue Firma (Registrierung + Betreiber-Firmenanlage) bekommt jetzt automatisch vier leere Genehmiger-Rollen mit erklärender Beschreibung (Datenschutzbeauftragter, Geschäftsführer, KI-Manager, CISO) - Admin muss nur noch Personen zuordnen statt bei null anzufangen. Welche Bedingung welche Rolle tatsächlich auslöst, bleibt weiterhin komplett konfigurierbar pro Firma (Migration 0018 fügt genehmiger_rolle.beschreibung als reines Freitext-Orientierungsfeld hinzu, keine feste fachliche Bindung). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
27
CLAUDE.md
27
CLAUDE.md
@@ -588,6 +588,33 @@ wie spezifiziert. Chromium-Headless-Screenshots (Desktop 1440×900 +
|
|||||||
Mobile 390×844) aller vier neuen/geänderten Seiten bestätigen zusätzlich
|
Mobile 390×844) aller vier neuen/geänderten Seiten bestätigen zusätzlich
|
||||||
das responsive Layout nach dem enconf-Card-Pattern.
|
das responsive Layout nach dem enconf-Card-Pattern.
|
||||||
|
|
||||||
|
**Standard-Genehmiger-Rollen bei Firmenanlage (2026-08-31, Migration
|
||||||
|
0018).** Jede neue Firma bekommt jetzt automatisch vier leere
|
||||||
|
Genehmiger-Rollen mit erklärender Beschreibung (`standardGenehmigerRollen`
|
||||||
|
in `internal/web/freigabe_handlers.go`, aufgerufen aus `handleRegister`
|
||||||
|
und `handleBetreiberAccountCreate` direkt nach `CreateAccount`):
|
||||||
|
|
||||||
|
- **Datenschutzbeauftragter** — "Prüft Anträge mit besonderen
|
||||||
|
Datenschutzanforderungen, z. B. wenn eine Datenschutz-Folgenabschätzung
|
||||||
|
erforderlich ist."
|
||||||
|
- **Geschäftsführer** — "Trifft die unternehmerische Entscheidung bei
|
||||||
|
besonders risikobehafteten Vorhaben."
|
||||||
|
- **KI-Manager** — "Bewertet den Antrag fachlich."
|
||||||
|
- **CISO** — "Prüft den Antrag aus Sicherheitssicht (Auditor)."
|
||||||
|
|
||||||
|
Bewusst **ohne** Mitglieder und **ohne** Freigabe-Regel — der Admin muss
|
||||||
|
nur noch Personen zuordnen (`/verwaltung/genehmiger-rollen`) und
|
||||||
|
optional eine auslösende Bedingung definieren
|
||||||
|
(`/verwaltung/freigabe-regeln`), statt bei null anzufangen. Welche
|
||||||
|
Bedingung welche Rolle tatsächlich auslöst, bleibt **vollständig
|
||||||
|
Sache der Firma** — das System schreibt keine feste fachliche Bindung
|
||||||
|
vor (z. B. keine hartkodierte Kopplung "Geschäftsführer ⇒ hochrisiko");
|
||||||
|
die Beschreibung ist reine UI-Orientierungshilfe, kein Ableitungsregel-
|
||||||
|
Ersatz. `genehmiger_rolle.beschreibung` ist ein freies, optionales
|
||||||
|
Textfeld (`NOT NULL DEFAULT ''`) — auch für vom Admin selbst angelegte
|
||||||
|
zusätzliche Rollen nutzbar. Getestet via `TestRegisterSeedsStandardGenehmigerRollen`
|
||||||
|
und live per curl gegen echten Server + Postgres verifiziert.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Onboarding
|
## Onboarding
|
||||||
|
|||||||
@@ -15,20 +15,23 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type GenehmigerRolle struct {
|
type GenehmigerRolle struct {
|
||||||
ID string
|
ID string
|
||||||
AccountID string
|
AccountID string
|
||||||
Name string
|
Name string
|
||||||
CreatedAt time.Time
|
Beschreibung string
|
||||||
|
CreatedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateGenehmigerRolle legt eine neue Freigabe-Funktion für einen
|
// CreateGenehmigerRolle legt eine neue Freigabe-Funktion für einen
|
||||||
// Mandanten an.
|
// Mandanten an. Beschreibung ist reiner Freitext zur Orientierung des
|
||||||
func (s *Store) CreateGenehmigerRolle(ctx context.Context, accountID, name string) (GenehmigerRolle, error) {
|
// Admins (z. B. "Prüft den Antrag aus Sicherheitssicht") — bindet keine
|
||||||
|
// Bedingung, das bleibt Sache von freigabe_regel.
|
||||||
|
func (s *Store) CreateGenehmigerRolle(ctx context.Context, accountID, name, beschreibung string) (GenehmigerRolle, error) {
|
||||||
var g GenehmigerRolle
|
var g GenehmigerRolle
|
||||||
err := s.Pool.QueryRow(ctx, `
|
err := s.Pool.QueryRow(ctx, `
|
||||||
INSERT INTO genehmiger_rolle (account_id, name) VALUES ($1, $2)
|
INSERT INTO genehmiger_rolle (account_id, name, beschreibung) VALUES ($1, $2, $3)
|
||||||
RETURNING id, account_id, name, created_at
|
RETURNING id, account_id, name, beschreibung, created_at
|
||||||
`, accountID, name).Scan(&g.ID, &g.AccountID, &g.Name, &g.CreatedAt)
|
`, accountID, name, beschreibung).Scan(&g.ID, &g.AccountID, &g.Name, &g.Beschreibung, &g.CreatedAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return GenehmigerRolle{}, fmt.Errorf("store: create genehmiger rolle: %w", err)
|
return GenehmigerRolle{}, fmt.Errorf("store: create genehmiger rolle: %w", err)
|
||||||
}
|
}
|
||||||
@@ -39,8 +42,8 @@ func (s *Store) CreateGenehmigerRolle(ctx context.Context, accountID, name strin
|
|||||||
func (s *Store) GetGenehmigerRolle(ctx context.Context, id string) (GenehmigerRolle, error) {
|
func (s *Store) GetGenehmigerRolle(ctx context.Context, id string) (GenehmigerRolle, error) {
|
||||||
var g GenehmigerRolle
|
var g GenehmigerRolle
|
||||||
err := s.Pool.QueryRow(ctx, `
|
err := s.Pool.QueryRow(ctx, `
|
||||||
SELECT id, account_id, name, created_at FROM genehmiger_rolle WHERE id = $1
|
SELECT id, account_id, name, beschreibung, created_at FROM genehmiger_rolle WHERE id = $1
|
||||||
`, id).Scan(&g.ID, &g.AccountID, &g.Name, &g.CreatedAt)
|
`, id).Scan(&g.ID, &g.AccountID, &g.Name, &g.Beschreibung, &g.CreatedAt)
|
||||||
if errors.Is(err, pgx.ErrNoRows) {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
return GenehmigerRolle{}, ErrNotFound
|
return GenehmigerRolle{}, ErrNotFound
|
||||||
}
|
}
|
||||||
@@ -54,7 +57,7 @@ func (s *Store) GetGenehmigerRolle(ctx context.Context, id string) (GenehmigerRo
|
|||||||
// Mandanten.
|
// Mandanten.
|
||||||
func (s *Store) ListGenehmigerRollenForAccount(ctx context.Context, accountID string) ([]GenehmigerRolle, error) {
|
func (s *Store) ListGenehmigerRollenForAccount(ctx context.Context, accountID string) ([]GenehmigerRolle, error) {
|
||||||
rows, err := s.Pool.Query(ctx, `
|
rows, err := s.Pool.Query(ctx, `
|
||||||
SELECT id, account_id, name, created_at FROM genehmiger_rolle
|
SELECT id, account_id, name, beschreibung, created_at FROM genehmiger_rolle
|
||||||
WHERE account_id = $1 ORDER BY name
|
WHERE account_id = $1 ORDER BY name
|
||||||
`, accountID)
|
`, accountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -65,7 +68,7 @@ func (s *Store) ListGenehmigerRollenForAccount(ctx context.Context, accountID st
|
|||||||
var out []GenehmigerRolle
|
var out []GenehmigerRolle
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var g GenehmigerRolle
|
var g GenehmigerRolle
|
||||||
if err := rows.Scan(&g.ID, &g.AccountID, &g.Name, &g.CreatedAt); err != nil {
|
if err := rows.Scan(&g.ID, &g.AccountID, &g.Name, &g.Beschreibung, &g.CreatedAt); err != nil {
|
||||||
return nil, fmt.Errorf("store: scan genehmiger rolle: %w", err)
|
return nil, fmt.Errorf("store: scan genehmiger rolle: %w", err)
|
||||||
}
|
}
|
||||||
out = append(out, g)
|
out = append(out, g)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ func TestGenehmigerRolleCRUDAndMitgliedschaft(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
accID := testAccountID(t, s)
|
accID := testAccountID(t, s)
|
||||||
|
|
||||||
g, err := s.CreateGenehmigerRolle(ctx, accID, "Datenschutzbeauftragter")
|
g, err := s.CreateGenehmigerRolle(ctx, accID, "Datenschutzbeauftragter", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("CreateGenehmigerRolle: %v", err)
|
t.Fatalf("CreateGenehmigerRolle: %v", err)
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,7 @@ func TestFreigabeRegelCRUD(t *testing.T) {
|
|||||||
s := openTestStore(t)
|
s := openTestStore(t)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
accID := testAccountID(t, s)
|
accID := testAccountID(t, s)
|
||||||
g, err := s.CreateGenehmigerRolle(ctx, accID, "Geschaeftsfuehrung")
|
g, err := s.CreateGenehmigerRolle(ctx, accID, "Geschaeftsfuehrung", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("CreateGenehmigerRolle: %v", err)
|
t.Fatalf("CreateGenehmigerRolle: %v", err)
|
||||||
}
|
}
|
||||||
@@ -113,8 +113,8 @@ func TestFreigabeschrittLifecycleUndKaskade(t *testing.T) {
|
|||||||
}
|
}
|
||||||
antragID := antrag.ID
|
antragID := antrag.ID
|
||||||
|
|
||||||
dsb, _ := s.CreateGenehmigerRolle(ctx, accID, "Datenschutzbeauftragter")
|
dsb, _ := s.CreateGenehmigerRolle(ctx, accID, "Datenschutzbeauftragter", "")
|
||||||
gf, _ := s.CreateGenehmigerRolle(ctx, accID, "Geschaeftsfuehrung")
|
gf, _ := s.CreateGenehmigerRolle(ctx, accID, "Geschaeftsfuehrung", "")
|
||||||
|
|
||||||
schrittDSB, err := s.CreateFreigabeschritt(ctx, antragID, dsb.ID)
|
schrittDSB, err := s.CreateFreigabeschritt(ctx, antragID, dsb.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE genehmiger_rolle DROP COLUMN beschreibung;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE genehmiger_rolle ADD COLUMN beschreibung TEXT NOT NULL DEFAULT '';
|
||||||
@@ -66,6 +66,11 @@ func (s *Server) handleRegister(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := s.seedStandardGenehmigerRollen(ctx, acc.ID); err != nil {
|
||||||
|
s.renderAuthPage(w, "register", authPageData{Title: "Registrieren", Error: "Genehmiger-Rollen konnten nicht angelegt werden"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err := s.startSession(w, r, user.ID); err != nil {
|
if err := s.startSession(w, r, user.ID); err != nil {
|
||||||
s.renderAuthPage(w, "register", authPageData{Title: "Registrieren", Error: "Sitzung konnte nicht gestartet werden"})
|
s.renderAuthPage(w, "register", authPageData{Title: "Registrieren", Error: "Sitzung konnte nicht gestartet werden"})
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -147,6 +147,13 @@ func (s *Server) handleBetreiberAccountCreate(w http.ResponseWriter, r *http.Req
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err := s.seedStandardGenehmigerRollen(ctx, acc.ID); err != nil {
|
||||||
|
data := betreiberAccountNeuData{Title: "Firma anlegen", Nav: navFor(r), Error: "Genehmiger-Rollen konnten nicht angelegt werden: " + err.Error()}
|
||||||
|
if err := s.templates.ExecuteTemplate(w, "betreiber-account-neu", data); err != nil {
|
||||||
|
http.Error(w, "Seite konnte nicht gerendert werden", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
if _, err := s.store.CreateAuditEntry(ctx, currentUser(r).ID, "betreiber_firma_angelegt", "account", acc.ID, accountName+" / "+email); err != nil {
|
if _, err := s.store.CreateAuditEntry(ctx, currentUser(r).ID, "betreiber_firma_angelegt", "account", acc.ID, accountName+" / "+email); err != nil {
|
||||||
http.Error(w, "Audit-Log konnte nicht geschrieben werden: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "Audit-Log konnte nicht geschrieben werden: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -9,18 +9,48 @@
|
|||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/netcell-it/deklarix/internal/store"
|
"github.com/netcell-it/deklarix/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// standardGenehmigerRollen wird bei jeder neuen Firma automatisch angelegt
|
||||||
|
// (ohne Mitglieder/Freigabe-Regeln) — der Admin muss nur noch Personen
|
||||||
|
// zuordnen und bei Bedarf Freigabe-Regeln ergänzen, statt bei null
|
||||||
|
// anzufangen. Die Beschreibung ist reine Orientierungshilfe im UI; welche
|
||||||
|
// Bedingung welche Rolle tatsächlich auslöst, entscheidet weiterhin jede
|
||||||
|
// Firma selbst über /verwaltung/freigabe-regeln — das System schreibt
|
||||||
|
// keine fachliche Bindung vor.
|
||||||
|
var standardGenehmigerRollen = []struct {
|
||||||
|
Name string
|
||||||
|
Beschreibung string
|
||||||
|
}{
|
||||||
|
{"Datenschutzbeauftragter", "Prüft Anträge mit besonderen Datenschutzanforderungen, z. B. wenn eine Datenschutz-Folgenabschätzung erforderlich ist."},
|
||||||
|
{"Geschäftsführer", "Trifft die unternehmerische Entscheidung bei besonders risikobehafteten Vorhaben."},
|
||||||
|
{"KI-Manager", "Bewertet den Antrag fachlich."},
|
||||||
|
{"CISO", "Prüft den Antrag aus Sicherheitssicht (Auditor)."},
|
||||||
|
}
|
||||||
|
|
||||||
|
// seedStandardGenehmigerRollen wird direkt nach dem Anlegen einer neuen
|
||||||
|
// Firma aufgerufen (öffentliche Registrierung und Betreiber-Firmenanlage).
|
||||||
|
func (s *Server) seedStandardGenehmigerRollen(ctx context.Context, accountID string) error {
|
||||||
|
for _, r := range standardGenehmigerRollen {
|
||||||
|
if _, err := s.store.CreateGenehmigerRolle(ctx, accountID, r.Name, r.Beschreibung); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Admin: Genehmiger-Rollen ───────────────────────────────────────
|
// ─── Admin: Genehmiger-Rollen ───────────────────────────────────────
|
||||||
|
|
||||||
type genehmigerRolleItem struct {
|
type genehmigerRolleItem struct {
|
||||||
ID string
|
ID string
|
||||||
Name string
|
Name string
|
||||||
Mitglieder []nutzerOption
|
Beschreibung string
|
||||||
|
Mitglieder []nutzerOption
|
||||||
}
|
}
|
||||||
|
|
||||||
type nutzerOption struct {
|
type nutzerOption struct {
|
||||||
@@ -53,7 +83,7 @@ func (s *Server) handleGenehmigerRollenListe(w http.ResponseWriter, r *http.Requ
|
|||||||
data.Nutzer = append(data.Nutzer, nutzerOption{ID: u.ID, Email: u.Email})
|
data.Nutzer = append(data.Nutzer, nutzerOption{ID: u.ID, Email: u.Email})
|
||||||
}
|
}
|
||||||
for _, g := range rollen {
|
for _, g := range rollen {
|
||||||
item := genehmigerRolleItem{ID: g.ID, Name: g.Name}
|
item := genehmigerRolleItem{ID: g.ID, Name: g.Name, Beschreibung: g.Beschreibung}
|
||||||
mitglieder, err := s.store.ListNutzerForGenehmigerRolle(r.Context(), g.ID)
|
mitglieder, err := s.store.ListNutzerForGenehmigerRolle(r.Context(), g.ID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for _, m := range mitglieder {
|
for _, m := range mitglieder {
|
||||||
@@ -77,7 +107,8 @@ func (s *Server) handleGenehmigerRolleCreate(w http.ResponseWriter, r *http.Requ
|
|||||||
http.Error(w, "Name ist Pflicht", http.StatusBadRequest)
|
http.Error(w, "Name ist Pflicht", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if _, err := s.store.CreateGenehmigerRolle(r.Context(), currentUser(r).AccountID, name); err != nil {
|
beschreibung := strings.TrimSpace(r.FormValue("beschreibung"))
|
||||||
|
if _, err := s.store.CreateGenehmigerRolle(r.Context(), currentUser(r).AccountID, name, beschreibung); err != nil {
|
||||||
http.Error(w, "Genehmiger-Rolle konnte nicht angelegt werden: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "Genehmiger-Rolle konnte nicht angelegt werden: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ func TestFreigabeWorkflowGatesBisAlleZugestimmtHaben(t *testing.T) {
|
|||||||
accountID, antragID, verantwortlicherCookie := seedFreigabeSzenario(t, fs, s)
|
accountID, antragID, verantwortlicherCookie := seedFreigabeSzenario(t, fs, s)
|
||||||
werkzeugID := werkzeugIDFor(fs, "ChatGPT Enterprise")
|
werkzeugID := werkzeugIDFor(fs, "ChatGPT Enterprise")
|
||||||
|
|
||||||
dsb, err := fs.CreateGenehmigerRolle(context.Background(), accountID, "Datenschutzbeauftragter")
|
dsb, err := fs.CreateGenehmigerRolle(context.Background(), accountID, "Datenschutzbeauftragter", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("CreateGenehmigerRolle: %v", err)
|
t.Fatalf("CreateGenehmigerRolle: %v", err)
|
||||||
}
|
}
|
||||||
@@ -166,8 +166,8 @@ func TestFreigabeAblehnungKipptGesamtenAntrag(t *testing.T) {
|
|||||||
accountID, antragID, verantwortlicherCookie := seedFreigabeSzenario(t, fs, s)
|
accountID, antragID, verantwortlicherCookie := seedFreigabeSzenario(t, fs, s)
|
||||||
werkzeugID := werkzeugIDFor(fs, "ChatGPT Enterprise")
|
werkzeugID := werkzeugIDFor(fs, "ChatGPT Enterprise")
|
||||||
|
|
||||||
dsb, _ := fs.CreateGenehmigerRolle(context.Background(), accountID, "Datenschutzbeauftragter")
|
dsb, _ := fs.CreateGenehmigerRolle(context.Background(), accountID, "Datenschutzbeauftragter", "")
|
||||||
gf, _ := fs.CreateGenehmigerRolle(context.Background(), accountID, "Geschaeftsfuehrung")
|
gf, _ := fs.CreateGenehmigerRolle(context.Background(), accountID, "Geschaeftsfuehrung", "")
|
||||||
fs.CreateFreigabeRegel(context.Background(), accountID, "anforderung", "dsfa_erforderlich", dsb.ID)
|
fs.CreateFreigabeRegel(context.Background(), accountID, "anforderung", "dsfa_erforderlich", dsb.ID)
|
||||||
fs.CreateFreigabeRegel(context.Background(), accountID, "datenklasse", "besondere_kategorie", gf.ID)
|
fs.CreateFreigabeRegel(context.Background(), accountID, "datenklasse", "besondere_kategorie", gf.ID)
|
||||||
|
|
||||||
@@ -223,7 +223,7 @@ func TestFreigabeEntscheidenOhneRolleWirdAbgelehnt(t *testing.T) {
|
|||||||
accountID, antragID, verantwortlicherCookie := seedFreigabeSzenario(t, fs, s)
|
accountID, antragID, verantwortlicherCookie := seedFreigabeSzenario(t, fs, s)
|
||||||
werkzeugID := werkzeugIDFor(fs, "ChatGPT Enterprise")
|
werkzeugID := werkzeugIDFor(fs, "ChatGPT Enterprise")
|
||||||
|
|
||||||
dsb, _ := fs.CreateGenehmigerRolle(context.Background(), accountID, "Datenschutzbeauftragter")
|
dsb, _ := fs.CreateGenehmigerRolle(context.Background(), accountID, "Datenschutzbeauftragter", "")
|
||||||
fs.CreateFreigabeRegel(context.Background(), accountID, "anforderung", "dsfa_erforderlich", dsb.ID)
|
fs.CreateFreigabeRegel(context.Background(), accountID, "anforderung", "dsfa_erforderlich", dsb.ID)
|
||||||
|
|
||||||
fremdCookie := seedUserInAccount(t, fs, accountID, "fremd@example.com", "mitarbeiter")
|
fremdCookie := seedUserInAccount(t, fs, accountID, "fremd@example.com", "mitarbeiter")
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ type Store interface {
|
|||||||
CreateRegistereintrag(ctx context.Context, in store.RegistereintragInput) (store.Registereintrag, error)
|
CreateRegistereintrag(ctx context.Context, in store.RegistereintragInput) (store.Registereintrag, error)
|
||||||
ListRegistereintraegeForAccount(ctx context.Context, accountID string) ([]store.Registereintrag, error)
|
ListRegistereintraegeForAccount(ctx context.Context, accountID string) ([]store.Registereintrag, error)
|
||||||
|
|
||||||
CreateGenehmigerRolle(ctx context.Context, accountID, name string) (store.GenehmigerRolle, error)
|
CreateGenehmigerRolle(ctx context.Context, accountID, name, beschreibung string) (store.GenehmigerRolle, error)
|
||||||
GetGenehmigerRolle(ctx context.Context, id string) (store.GenehmigerRolle, error)
|
GetGenehmigerRolle(ctx context.Context, id string) (store.GenehmigerRolle, error)
|
||||||
ListGenehmigerRollenForAccount(ctx context.Context, accountID string) ([]store.GenehmigerRolle, error)
|
ListGenehmigerRollenForAccount(ctx context.Context, accountID string) ([]store.GenehmigerRolle, error)
|
||||||
DeleteGenehmigerRolle(ctx context.Context, id string) error
|
DeleteGenehmigerRolle(ctx context.Context, id string) error
|
||||||
|
|||||||
@@ -556,10 +556,10 @@ func (f *fakeStore) ListRegistereintraegeForAccount(ctx context.Context, account
|
|||||||
return f.registereintraege[accountID], nil
|
return f.registereintraege[accountID], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeStore) CreateGenehmigerRolle(ctx context.Context, accountID, name string) (store.GenehmigerRolle, error) {
|
func (f *fakeStore) CreateGenehmigerRolle(ctx context.Context, accountID, name, beschreibung string) (store.GenehmigerRolle, error) {
|
||||||
f.mu.Lock()
|
f.mu.Lock()
|
||||||
defer f.mu.Unlock()
|
defer f.mu.Unlock()
|
||||||
g := store.GenehmigerRolle{ID: f.newID(), AccountID: accountID, Name: name, CreatedAt: time.Now()}
|
g := store.GenehmigerRolle{ID: f.newID(), AccountID: accountID, Name: name, Beschreibung: beschreibung, CreatedAt: time.Now()}
|
||||||
f.genehmigerRollen[g.ID] = g
|
f.genehmigerRollen[g.ID] = g
|
||||||
return g, nil
|
return g, nil
|
||||||
}
|
}
|
||||||
@@ -944,6 +944,53 @@ func TestRegisterThenLoginThenAccessProtectedPage(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRegisterSeedsStandardGenehmigerRollen(t *testing.T) {
|
||||||
|
fs := newFakeStore()
|
||||||
|
s := newServer(t, fs)
|
||||||
|
|
||||||
|
regResp := postForm(t, s, nil, "/register", url.Values{
|
||||||
|
"account_name": {"Meine Firma"}, "email": {"neu2@example.com"},
|
||||||
|
"password": {"ein-sicheres-passwort"},
|
||||||
|
})
|
||||||
|
if regResp.Code != http.StatusSeeOther {
|
||||||
|
t.Fatalf("register status = %d, want 303, body: %s", regResp.Code, regResp.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
var accountID string
|
||||||
|
for _, u := range fs.users {
|
||||||
|
if u.Email == "neu2@example.com" {
|
||||||
|
accountID = u.AccountID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if accountID == "" {
|
||||||
|
t.Fatal("neuer Nutzer nicht gefunden")
|
||||||
|
}
|
||||||
|
|
||||||
|
rollen, err := fs.ListGenehmigerRollenForAccount(context.Background(), accountID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ListGenehmigerRollenForAccount: %v", err)
|
||||||
|
}
|
||||||
|
wantNamen := []string{"Datenschutzbeauftragter", "Geschäftsführer", "KI-Manager", "CISO"}
|
||||||
|
if len(rollen) != len(wantNamen) {
|
||||||
|
t.Fatalf("got %d Genehmiger-Rollen, want %d", len(rollen), len(wantNamen))
|
||||||
|
}
|
||||||
|
var namen []string
|
||||||
|
for _, r := range rollen {
|
||||||
|
namen = append(namen, r.Name)
|
||||||
|
}
|
||||||
|
for _, want := range wantNamen {
|
||||||
|
found := false
|
||||||
|
for _, n := range namen {
|
||||||
|
if n == want {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("Standard-Rolle %q wurde nicht angelegt, vorhanden: %v", want, namen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRegisterRejectsDuplicateEmail(t *testing.T) {
|
func TestRegisterRejectsDuplicateEmail(t *testing.T) {
|
||||||
fs := newFakeStore()
|
fs := newFakeStore()
|
||||||
s := newServer(t, fs)
|
s := newServer(t, fs)
|
||||||
|
|||||||
@@ -27,6 +27,10 @@
|
|||||||
<label for="name">Name</label>
|
<label for="name">Name</label>
|
||||||
<input type="text" id="name" name="name" placeholder="z. B. Datenschutzbeauftragter" required>
|
<input type="text" id="name" name="name" placeholder="z. B. Datenschutzbeauftragter" required>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="beschreibung">Beschreibung (optional)</label>
|
||||||
|
<input type="text" id="beschreibung" name="beschreibung" placeholder="z. B. Prüft den Antrag aus Sicherheitssicht">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
@@ -42,7 +46,7 @@
|
|||||||
{{range .Rollen}}
|
{{range .Rollen}}
|
||||||
{{$rolleID := .ID}}
|
{{$rolleID := .ID}}
|
||||||
<li>
|
<li>
|
||||||
<div class="beteiligter-kopf"><strong>{{.Name}}</strong></div>
|
<div class="beteiligter-kopf"><strong>{{.Name}}</strong>{{if .Beschreibung}} — {{.Beschreibung}}{{end}}</div>
|
||||||
{{if .Mitglieder}}
|
{{if .Mitglieder}}
|
||||||
<ul class="beitraege-liste">
|
<ul class="beitraege-liste">
|
||||||
{{range .Mitglieder}}
|
{{range .Mitglieder}}
|
||||||
|
|||||||
Reference in New Issue
Block a user