package store import ( "context" "errors" "fmt" "time" "github.com/jackc/pgx/v5" ) // ErrNotFound signalisiert, dass eine Abfrage keine Zeile ergeben hat — // abgrenzbar von echten Fehlern (z. B. Verbindungsabbruch), damit // Aufrufer "nicht gefunden" gezielt anders behandeln können (z. B. bei // Login: falsche E-Mail vs. Datenbankfehler). var ErrNotFound = errors.New("store: nicht gefunden") // User ist ein Login innerhalb eines Account (Mandanten). type User struct { ID string AccountID string Email string PasswordHash string Role string CreatedAt time.Time } // CreateUser legt einen neuen Nutzer innerhalb eines Accounts an. // passwordHash muss bereits gehasht sein (siehe internal/auth) — store // speichert nur, es hasht nicht selbst. func (s *Store) CreateUser(ctx context.Context, accountID, email, passwordHash, role string) (User, error) { var u User err := s.Pool.QueryRow(ctx, ` INSERT INTO app_user (account_id, email, password_hash, role) VALUES ($1, $2, $3, $4) RETURNING id, account_id, email, password_hash, role, created_at `, accountID, email, passwordHash, role).Scan( &u.ID, &u.AccountID, &u.Email, &u.PasswordHash, &u.Role, &u.CreatedAt, ) if err != nil { return User{}, fmt.Errorf("store: create user: %w", err) } return u, nil } // GetUserByEmail liest einen Nutzer anhand seiner E-Mail-Adresse. // Liefert ErrNotFound, wenn keine E-Mail passt (kein Datenbankfehler). func (s *Store) GetUserByEmail(ctx context.Context, email string) (User, error) { var u User err := s.Pool.QueryRow(ctx, ` SELECT id, account_id, email, password_hash, role, created_at FROM app_user WHERE email = $1 `, email).Scan(&u.ID, &u.AccountID, &u.Email, &u.PasswordHash, &u.Role, &u.CreatedAt) if errors.Is(err, pgx.ErrNoRows) { return User{}, ErrNotFound } if err != nil { return User{}, fmt.Errorf("store: get user by email: %w", err) } return u, nil } // GetUser liest einen Nutzer anhand seiner ID. func (s *Store) GetUser(ctx context.Context, id string) (User, error) { var u User err := s.Pool.QueryRow(ctx, ` SELECT id, account_id, email, password_hash, role, created_at FROM app_user WHERE id = $1 `, id).Scan(&u.ID, &u.AccountID, &u.Email, &u.PasswordHash, &u.Role, &u.CreatedAt) if errors.Is(err, pgx.ErrNoRows) { return User{}, ErrNotFound } if err != nil { return User{}, fmt.Errorf("store: get user: %w", err) } return u, nil } // ListUsersForAccount liefert alle Logins eines Mandanten — für den // Admin-Bereich (Account-Detailansicht). func (s *Store) ListUsersForAccount(ctx context.Context, accountID string) ([]User, error) { rows, err := s.Pool.Query(ctx, ` SELECT id, account_id, email, password_hash, role, created_at FROM app_user WHERE account_id = $1 ORDER BY created_at `, accountID) if err != nil { return nil, fmt.Errorf("store: list users for account: %w", err) } defer rows.Close() var out []User for rows.Next() { var u User if err := rows.Scan(&u.ID, &u.AccountID, &u.Email, &u.PasswordHash, &u.Role, &u.CreatedAt); err != nil { return nil, fmt.Errorf("store: scan user: %w", err) } out = append(out, u) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("store: list users for account: %w", err) } return out, nil }