gateway/docs/real-estate-feature-integration-guide/03-interfaces.md

30 KiB

Schritt 2: Interface erstellen

← Zurück: Datenmodell erstellen | Weiter: Feature-Logik implementieren →

Übersicht: Was sind Interfaces?

Interfaces sind aktive Klassen, die den Datenbankzugriff implementieren. Sie unterscheiden sich von Datamodels (die nur die Datenstruktur definieren):

Aspekt Datamodels Interfaces
Zweck Definiert WAS (Datenstruktur) Implementiert WIE (Datenzugriff)
Inhalt Pydantic-Modelle mit Feldern und Validierung Klassen mit CRUD-Methoden (create, get, update, delete)
Beispiel class Projekt(BaseModel): ... def createProjekt(...) -> Projekt: ...
Aktivität Passiv (nur Struktur) Aktiv (führt Operationen aus)

Analogie:

  • Datamodel = Bauplan (beschreibt das Haus)
  • Interface = Bauunternehmer (baut das Haus)

Struktur: Real Estate CRUD-Interface

Da das Feature stateless arbeitet, benötigen wir nur ein Interface für CRUD-Operationen auf Real Estate-Entitäten:

Real Estate-Datenmodelle → Real Estate CRUD-Interface

Datamodel: datamodelRealEstate.py

  • Projekt
  • Parzelle
  • Dokument
  • Kanton, Gemeinde, Land
  • GeoPolylinie, GeoPunkt
  • Kontext
  • etc.

Interface: interfaceDbRealEstateObjects.py

  • RealEstateObjects (Haupt-Interface)
  • RealEstateAccess (Zugriffskontrolle)
  • Methoden: createProjekt(), getParzelle(), updateDokument(), etc.

Optional: interfaceDbRealEstateChatObjects.py (nur für direkte SQL-Queries)

  • RealEstateChatObjects - Für direkte Query-Ausführung ohne Session
  • Methoden: executeQuery() - Führt SQL direkt aus

Warum nur ein Haupt-Interface?

  1. Stateless Design:

    • Keine Session-Verwaltung notwendig
    • Direkte CRUD-Operationen auf Real Estate-Modellen
  2. Einfache Architektur:

    • Ein Interface für alle CRUD-Operationen
    • Weniger Komplexität, bessere Wartbarkeit
  3. Optionales Query-Interface:

    • Nur für direkte SQL-Queries (stateless)
    • Keine Session-Management-Funktionen

Zu erstellende Dateien

Schritt 2a: Real Estate CRUD-Interface (ERFORDERLICH)

Zwei separate Dateien (wie bei anderen Features):

Datei 1: modules/interfaces/interfaceDbRealEstateAccess.py

Enthält:

  • RealEstateAccess - Zugriffskontrolle für Real Estate-Entitäten
  • Methoden: uam(), canModify()

Zweck: Prüft Zugriffsrechte und filtert Daten basierend auf Benutzerprivilegien

Datei 2: modules/interfaces/interfaceDbRealEstateObjects.py

Enthält:

  • RealEstateObjects - Haupt-Interface für CRUD-Operationen
  • getInterface() - Factory-Funktion
  • Nutzt RealEstateAccess aus der Access-Datei

Zweck: Verwaltet Real Estate-Entitäten (Projekt, Parzelle, Dokument, etc.)

Nutzt:

  • datamodelRealEstate.py (Projekt, Parzelle, Dokument, etc.)
  • interfaceDbRealEstateAccess.py (für Zugriffskontrolle)

Wann benötigt: Für alle CRUD-Operationen auf Real Estate-Entitäten (z.B. Projekte erstellen/bearbeiten, Parzellen verwalten). Dies ist das Haupt-Interface für das Feature.


Schritt 2b: Query-Interface (OPTIONAL - nur für direkte SQL-Queries)

Eine Datei für stateless Query-Ausführung:

Datei: modules/interfaces/interfaceDbRealEstateChatObjects.py

Enthält:

  • RealEstateChatObjects - Interface für direkte SQL-Query-Ausführung
  • getInterface() - Factory-Funktion
  • Methoden: executeQuery() - Führt SQL direkt aus (stateless)

Zweck: Direkte SQL-Query-Ausführung ohne Session-Management

Nutzt:

  • connectorDbPostgre.DatabaseConnector für direkte SQL-Ausführung
  • Keine Chat-Modelle (stateless)

Wann benötigt: Nur wenn Sie direkte SQL-Queries ausführen möchten (z.B. für komplexe SELECT-Queries). Für CRUD-Operationen verwenden Sie das Real Estate CRUD-Interface.


Übersicht: Dateien und ihre Beziehungen

┌─────────────────────────────────────────────────────────────┐
│ DATAMODELS (Struktur)                                       │
├─────────────────────────────────────────────────────────────┤
│ datamodelRealEstate.py                                      │
│   ├── Projekt                                               │
│   ├── Parzelle                                              │
│   ├── Dokument                                              │
│   ├── Kanton, Gemeinde, Land                               │
│   ├── GeoPolylinie, GeoPunkt                               │
│   ├── Kontext                                               │
│   └── ...                                                   │
└─────────────────────────────────────────────────────────────┘
                        │
                        │ nutzt
                        ▼
┌─────────────────────────────────────────────────────────────┐
│ INTERFACES (Zugriff)                                         │
├─────────────────────────────────────────────────────────────┤
│ REAL ESTATE CRUD-INTERFACE (ERFORDERLICH)                 │
│                                                              │
│ interfaceDbRealEstateAccess.py                              │
│   └── RealEstateAccess                                      │
│       ├── uam()                                             │
│       └── canModify()                                       │
│                                                              │
│ interfaceDbRealEstateObjects.py                             │
│   ├── RealEstateObjects                                     │
│   │   ├── createProjekt()                                   │
│   │   ├── getProjekt()                                      │
│   │   ├── updateProjekt()                                   │
│   │   ├── deleteProjekt()                                   │
│   │   ├── createParzelle()                                  │
│   │   ├── getParzelle()                                     │
│   │   └── ... (CRUD für alle Entitäten)                    │
│   └── getInterface()                                        │
│       └── nutzt RealEstateAccess                            │
│                                                              │
│ QUERY-INTERFACE (OPTIONAL - nur für direkte SQL)           │
│                                                              │
│ interfaceDbRealEstateChatObjects.py                        │
│   ├── RealEstateChatObjects                                 │
│   │   └── executeQuery()  # Direkte SQL-Ausführung         │
│   └── getInterface()                                        │
│       └── Keine Access-Klasse (stateless)                   │
└─────────────────────────────────────────────────────────────┘

Interface-Struktur: Access vs. Objects

Jedes Interface besteht aus zwei Klassen:

1. *Access Klasse (Zugriffskontrolle)

Zweck: Prüft, wer was sehen/dürfen darf

Methoden:

  • uam() - Filtert Daten basierend auf Benutzerprivilegien
  • canModify() - Prüft, ob Benutzer ändern darf

Beispiel: RealEstateChatAccess

2. *Objects Klasse (Haupt-Interface)

Zweck: Führt CRUD-Operationen aus

Methoden:

  • create*() - Erstellt neue Einträge
  • get*() - Lädt Einträge
  • update*() - Aktualisiert Einträge
  • delete*() - Löscht Einträge

Nutzt: *Access für Zugriffskontrolle

Beispiel: RealEstateChatObjects

Warum getrennt?

  • Separation of Concerns: Zugriffskontrolle ist separate Verantwortlichkeit
  • Wiederverwendbarkeit: Access-Klasse kann von mehreren Interfaces genutzt werden
  • Testbarkeit: Zugriffskontrolle kann unabhängig getestet werden

Implementierung: Real Estate CRUD-Interface

Das Real Estate CRUD-Interface besteht aus zwei separaten Dateien, genau wie bei anderen Features (interfaceDbAppObjects.py + interfaceDbAppAccess.py).

Datei 1: Access-Implementierung

Datei: modules/interfaces/interfaceDbRealEstateAccess.py

"""
Access control for Real Estate interface.
Handles user access management and permission checks.
"""

import logging
from typing import Dict, Any, List, Optional
from modules.datamodels.datamodelUam import User, UserPrivilege

logger = logging.getLogger(__name__)


class RealEstateAccess:
    """
    Access control class for Real Estate interface.
    Handles user access management and permission checks.
    """
    
    def __init__(self, currentUser: User, db):
        """Initialize with user context."""
        self.currentUser = currentUser
        self.mandateId = currentUser.mandateId
        self.userId = currentUser.id
        
        if not self.mandateId or not self.userId:
            raise ValueError("Invalid user context: mandateId and userId are required")
            
        self.db = db

    def uam(self, model_class: type, recordset: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """
        Unified user access management function that filters data based on user privileges.
        
        Args:
            model_class: Pydantic model class for the table
            recordset: Recordset to filter based on access rules
            
        Returns:
            Filtered recordset with access control attributes
        """
        from modules.datamodels.datamodelUam import UserPrivilege
        
        userPrivilege = self.currentUser.privilege
        filtered_records = []
        
        # System admins see all records
        if userPrivilege == UserPrivilege.SYSADMIN:
            filtered_records = recordset
        # Admins see records in their mandate
        elif userPrivilege == UserPrivilege.ADMIN:
            filtered_records = [r for r in recordset if r.get("mandateId", "-") == self.mandateId]
        # Regular users see only their records
        else:
            filtered_records = [
                r for r in recordset 
                if r.get("mandateId", "-") == self.mandateId and r.get("_createdBy") == self.userId
            ]
        
        # Add access control attributes
        for record in filtered_records:
            record["_hideView"] = False
            record["_hideEdit"] = not self.canModify(model_class, record.get("id"))
            record["_hideDelete"] = not self.canModify(model_class, record.get("id"))
        
        return filtered_records

    def canModify(self, model_class: type, recordId: Optional[str] = None) -> bool:
        """Checks if the current user can modify records."""
        from modules.datamodels.datamodelUam import UserPrivilege
        
        userPrivilege = self.currentUser.privilege
        
        if userPrivilege == UserPrivilege.SYSADMIN:
            return True
            
        if recordId is not None:
            records = self.db.getRecordset(model_class, recordFilter={"id": recordId})
            if not records:
                return False
                
            record = records[0]
            
            if userPrivilege == UserPrivilege.ADMIN and record.get("mandateId", "-") == self.mandateId:
                return True
                
            if (record.get("mandateId", "-") == self.mandateId and 
                record.get("_createdBy") == self.userId):
                return True
                
            return False
        else:
            return True  # Regular users can create records

Datei 2: Objects-Implementierung

Datei: modules/interfaces/interfaceDbRealEstateObjects.py

"""
Interface to Real Estate database objects.
Uses PostgreSQL connector for data access with user/mandate filtering.
Handles CRUD operations on Real Estate entities (Projekt, Parzelle, etc.).
"""

import logging
from typing import Dict, Any, List, Optional
from modules.datamodels.datamodelRealEstate import (
    Projekt,
    Parzelle,
    Dokument,
    Kanton,
    Gemeinde,
    Land,
    GeoPolylinie,
    GeoPunkt,
    Kontext,
    StatusProzess,
)
from modules.datamodels.datamodelUam import User
from modules.connectors.connectorDbPostgre import DatabaseConnector
from modules.shared.configuration import APP_CONFIG
# Import Access-Klasse aus separater Datei
from modules.interfaces.interfaceDbRealEstateAccess import RealEstateAccess

logger = logging.getLogger(__name__)

# Singleton factory for Real Estate interfaces
_realEstateInterfaces = {}


class RealEstateObjects:
    """
    Interface to Real Estate database objects.
    Uses PostgreSQL connector for data access with user/mandate filtering.
    Handles CRUD operations on Real Estate entities.
    """
    
    def __init__(self, currentUser: Optional[User] = None):
        """Initializes the Real Estate Interface."""
        self.currentUser = currentUser
        self.userId = currentUser.id if currentUser else None
        self.mandateId = currentUser.mandateId if currentUser else None
        self.access = None
        
        # Initialize database
        self._initializeDatabase()
        
        # Set user context if provided
        if currentUser:
            self.setUserContext(currentUser)

    def _initializeDatabase(self):
        """Initialize PostgreSQL database connection."""
        try:
            # Get database configuration from environment
            dbHost = APP_CONFIG.get("DB_APP_HOST", "localhost")
            dbDatabase = APP_CONFIG.get("DB_APP_DATABASE", "poweron_app")
            dbUser = APP_CONFIG.get("DB_APP_USER")
            dbPassword = APP_CONFIG.get("DB_APP_PASSWORD_SECRET")
            dbPort = int(APP_CONFIG.get("DB_APP_PORT", 5432))
            
            # Initialize database connector
            self.db = DatabaseConnector(
                dbHost=dbHost,
                dbDatabase=dbDatabase,
                dbUser=dbUser,
                dbPassword=dbPassword,
                dbPort=dbPort,
                userId=self.userId if self.userId else None,
            )
            
            logger.info(f"Real Estate database connector initialized for database: {dbDatabase}")
        except Exception as e:
            logger.error(f"Error initializing Real Estate database: {e}")
            raise

    def setUserContext(self, currentUser: User):
        """Sets the user context for the interface."""
        self.currentUser = currentUser
        self.userId = currentUser.id
        self.mandateId = currentUser.mandateId
        
        if not self.userId or not self.mandateId:
            raise ValueError("Invalid user context: id and mandateId are required")
        
        # Initialize access control
        self.access = RealEstateAccess(self.currentUser, self.db)
        
        # Update database context
        self.db.updateContext(self.userId)

    # ===== Projekt Methods =====
    
    def createProjekt(self, projekt: Projekt) -> Projekt:
        """Create a new project."""
        # Ensure mandateId is set
        if not projekt.mandateId:
            projekt.mandateId = self.mandateId
        
        # Apply access control
        self.access.uam(Projekt, [])
        
        # Save to database
        self.db.recordCreate(Projekt, projekt.model_dump())
        
        return projekt

    def getProjekt(self, projektId: str) -> Optional[Projekt]:
        """Get a project by ID."""
        records = self.db.getRecordset(
            Projekt,
            recordFilter={"id": projektId}
        )
        
        if not records:
            return None
        
        # Apply access control
        filtered = self.access.uam(Projekt, records)
        
        if not filtered:
            return None
        
        return Projekt(**filtered[0])

    def updateProjekt(self, projektId: str, updateData: Dict[str, Any]) -> Optional[Projekt]:
        """Update a project."""
        projekt = self.getProjekt(projektId)
        if not projekt:
            return None
        
        # Check if user can modify
        if not self.access.canModify(Projekt, projektId):
            raise PermissionError(f"User {self.userId} cannot modify project {projektId}")
        
        # Update fields
        for key, value in updateData.items():
            if hasattr(projekt, key):
                setattr(projekt, key, value)
        
        # Save to database
        self.db.recordModify(Projekt, projektId, projekt.model_dump())
        
        return projekt

    def deleteProjekt(self, projektId: str) -> bool:
        """Delete a project."""
        projekt = self.getProjekt(projektId)
        if not projekt:
            return False
        
        # Check if user can modify
        if not self.access.canModify(Projekt, projektId):
            raise PermissionError(f"User {self.userId} cannot delete project {projektId}")
        
        return self.db.recordDelete(Projekt, projektId)

    # ===== Parzelle Methods =====
    
    def createParzelle(self, parzelle: Parzelle) -> Parzelle:
        """Create a new plot."""
        if not parzelle.mandateId:
            parzelle.mandateId = self.mandateId
        
        self.access.uam(Parzelle, [])
        self.db.recordCreate(Parzelle, parzelle.model_dump())
        
        return parzelle

    def getParzelle(self, parzelleId: str) -> Optional[Parzelle]:
        """Get a plot by ID."""
        records = self.db.getRecordset(
            Parzelle,
            recordFilter={"id": parzelleId}
        )
        
        if not records:
            return None
        
        filtered = self.access.uam(Parzelle, records)
        
        if not filtered:
            return None
        
        return Parzelle(**filtered[0])

    def updateParzelle(self, parzelleId: str, updateData: Dict[str, Any]) -> Optional[Parzelle]:
        """Update a plot."""
        parzelle = self.getParzelle(parzelleId)
        if not parzelle:
            return None
        
        if not self.access.canModify(Parzelle, parzelleId):
            raise PermissionError(f"User {self.userId} cannot modify plot {parzelleId}")
        
        for key, value in updateData.items():
            if hasattr(parzelle, key):
                setattr(parzelle, key, value)
        
        self.db.recordModify(Parzelle, parzelleId, parzelle.model_dump())
        
        return parzelle

    def deleteParzelle(self, parzelleId: str) -> bool:
        """Delete a plot."""
        parzelle = self.getParzelle(parzelleId)
        if not parzelle:
            return False
        
        if not self.access.canModify(Parzelle, parzelleId):
            raise PermissionError(f"User {self.userId} cannot delete plot {parzelleId}")
        
        return self.db.recordDelete(Parzelle, parzelleId)

    # ===== Dokument Methods =====
    
    def createDokument(self, dokument: Dokument) -> Dokument:
        """Create a new document."""
        if not dokument.mandateId:
            dokument.mandateId = self.mandateId
        
        self.access.uam(Dokument, [])
        self.db.recordCreate(Dokument, dokument.model_dump())
        
        return dokument

    def getDokument(self, dokumentId: str) -> Optional[Dokument]:
        """Get a document by ID."""
        records = self.db.getRecordset(
            Dokument,
            recordFilter={"id": dokumentId}
        )
        
        if not records:
            return None
        
        filtered = self.access.uam(Dokument, records)
        
        if not filtered:
            return None
        
        return Dokument(**filtered[0])

    # ... weitere CRUD-Methoden für andere Entitäten (Kanton, Gemeinde, Land, etc.)


def getInterface(currentUser: User) -> RealEstateObjects:
    """
    Factory function to get or create a Real Estate interface instance for a user.
    Uses singleton pattern per user.
    """
    userKey = f"{currentUser.id}_{currentUser.mandateId}"
    
    if userKey not in _realEstateInterfaces:
        _realEstateInterfaces[userKey] = RealEstateObjects(currentUser)
    
    return _realEstateInterfaces[userKey]

Wichtige Punkte:

  1. DatabaseConnector: Nutzt connectorDbPostgre.DatabaseConnector für Datenbankzugriff
  2. Access Control: RealEstateAccess implementiert Benutzer- und Mandaten-Filterung
  3. Singleton Pattern: getInterface() erstellt pro User eine Instanz
  4. CRUD-Operationen: recordCreate, recordModify, recordDelete, getRecordset vom Connector
  5. MandateId: Wird automatisch gesetzt, wenn nicht vorhanden

Schritt 2b: Query-Interface (OPTIONAL - nur für direkte SQL-Queries)

Wann benötigt?

Kurze Antwort: Nur wenn Sie direkte SQL-Queries ausführen möchten (z.B. für komplexe SELECT-Queries). Für CRUD-Operationen verwenden Sie das Real Estate CRUD-Interface.

Szenario 1: Alles über CRUD-Interface (EMPFOHLEN)

Strukturiert und sicher:

# User schreibt: "Erstelle Projekt 'Test'"
# Feature-Logik nutzt CRUD-Interface:
realEstateInterface = getRealEstateInterface(currentUser)
projekt = realEstateInterface.createProjekt(Projekt(
    mandateId=currentUser.mandateId,  # Automatisch gesetzt
    label="Test"  # Validierung durch Pydantic
))

Vorteile:

  • Validierung: Pydantic-Modelle prüfen alle Felder automatisch
  • Zugriffskontrolle: RealEstateAccess prüft Berechtigungen
  • Sicherheit: Kein SQL-Injection-Risiko
  • Geschäftslogik: Automatisches Setzen von Systemfeldern (_createdBy, _createdAt)
  • Typsicherheit: Fehler werden zur Entwicklungszeit erkannt
  • Wartbarkeit: Zentrale CRUD-Methoden, einfach zu testen

Szenario 2: Direkte SQL-Queries (OPTIONAL)

Nur für komplexe SELECT-Queries:

# Für komplexe Queries, die nicht über CRUD-Methoden abgedeckt sind
chatInterface = getChatInterface(currentUser)
results = chatInterface.executeQuery(
    "SELECT p.*, COUNT(parz.id) as parzellen_count FROM Projekt p LEFT JOIN Parzelle parz ON parz.projektId = p.id GROUP BY p.id"
)

Warnung:

  • ⚠️ Nur für SELECT-Queries: Keine INSERT/UPDATE/DELETE über direkte SQL-Queries
  • ⚠️ Validierung erforderlich: Queries sollten validiert werden
  • ⚠️ SQL-Injection-Risiko: Immer Parameterisierung verwenden

Empfehlung

Sie benötigen das Query-Interface, wenn Sie:

  • Komplexe SELECT-Queries benötigen (z.B. JOINs, Aggregationen)
  • Flexible Query-Ausführung benötigen (nicht über CRUD-Methoden abgedeckt)

Sie benötigen es NICHT, wenn Sie:

  • Nur CRUD-Operationen benötigen (verwenden Sie das CRUD-Interface)
  • Einfache Queries haben (können über CRUD-Methoden abgedeckt werden)

Für Production-Systeme:

  • CRUD-Operationen: Immer über CRUD-Interface
  • Komplexe Queries: Optional über Query-Interface (nur SELECT)

Struktur: Query-Interface (stateless)

Eine Datei für direkte SQL-Query-Ausführung:

Datei: modules/interfaces/interfaceDbRealEstateChatObjects.py

"""
Interface for direct SQL query execution (stateless).
Uses PostgreSQL connector for direct query execution without session management.
"""

import logging
from typing import Dict, Any, Optional
from modules.datamodels.datamodelUam import User
from modules.connectors.connectorDbPostgre import DatabaseConnector
from modules.shared.configuration import APP_CONFIG

logger = logging.getLogger(__name__)

# Singleton factory
_realEstateChatInterfaces = {}


class RealEstateChatObjects:
    """Interface for direct SQL query execution (stateless)."""
    
    def __init__(self, currentUser: Optional[User] = None):
        """Initialize the Query Interface."""
        self.currentUser = currentUser
        self.userId = currentUser.id if currentUser else None
        self.mandateId = currentUser.mandateId if currentUser else None
        
        # Initialize database
        self._initializeDatabase()
        
        # Set user context if provided
        if currentUser:
            self.setUserContext(currentUser)

    def _initializeDatabase(self):
        """Initialize PostgreSQL database connection."""
        try:
            dbHost = APP_CONFIG.get("DB_APP_HOST", "localhost")
            dbDatabase = APP_CONFIG.get("DB_APP_DATABASE", "poweron_app")
            dbUser = APP_CONFIG.get("DB_APP_USER")
            dbPassword = APP_CONFIG.get("DB_APP_PASSWORD_SECRET")
            dbPort = int(APP_CONFIG.get("DB_APP_PORT", 5432))
            
            self.db = DatabaseConnector(
                dbHost=dbHost,
                dbDatabase=dbDatabase,
                dbUser=dbUser,
                dbPassword=dbPassword,
                dbPort=dbPort,
                userId=self.userId if self.userId else None,
            )
            
            logger.info(f"Real Estate Query database connector initialized for database: {dbDatabase}")
        except Exception as e:
            logger.error(f"Error initializing Real Estate Query database: {e}")
            raise

    def setUserContext(self, currentUser: User):
        """Sets the user context for the interface."""
        self.currentUser = currentUser
        self.userId = currentUser.id
        self.mandateId = currentUser.mandateId
        
        if not self.userId or not self.mandateId:
            raise ValueError("Invalid user context: id and mandateId are required")
        
        # Update database context
        self.db.updateContext(self.userId)

    # ===== Database Query Execution =====
    
    def executeQuery(self, queryText: str, parameters: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
        """
        Execute a SQL query directly on the database (stateless).
        
        WARNING: This method executes raw SQL. Ensure proper validation and sanitization
        before calling this method. Consider implementing query whitelisting or
        only allowing SELECT statements for production use.
        
        Args:
            queryText: SQL query string (preferably SELECT only)
            parameters: Optional parameters for parameterized queries
            
        Returns:
            Dictionary with 'rows' (list of dicts), 'columns' (list of column names),
            'rowCount' (int), and 'executionTime' (float)
        """
        import time
        
        try:
            start_time = time.time()
            
            # Ensure connection is alive
            self.db._ensure_connection()
            
            with self.db.connection.cursor() as cursor:
                # Execute query
                if parameters:
                    # Use parameterized query for safety
                    cursor.execute(queryText, parameters)
                else:
                    cursor.execute(queryText)
                
                # Fetch results
                rows = cursor.fetchall()
                
                # Convert to list of dictionaries
                result_rows = [dict(row) for row in rows]
                
                # Get column names
                columns = [desc[0] for desc in cursor.description] if cursor.description else []
                
                execution_time = time.time() - start_time
                
                return {
                    "rows": result_rows,
                    "columns": columns,
                    "rowCount": len(result_rows),
                    "executionTime": execution_time,
                }
        except Exception as e:
            logger.error(f"Error executing query: {e}")
            raise


def getInterface(currentUser: User) -> RealEstateChatObjects:
    """
    Factory function to get or create a Query interface instance for a user.
    Uses singleton pattern per user.
    """
    userKey = f"{currentUser.id}_{currentUser.mandateId}"
    
    if userKey not in _realEstateChatInterfaces:
        _realEstateChatInterfaces[userKey] = RealEstateChatObjects(currentUser)
    
    return _realEstateChatInterfaces[userKey]

Hinweise zur Implementierung

  1. Stateless: Keine Session-Management-Funktionen
  2. Nur für Queries: Primär für SELECT-Queries gedacht
  3. Sicherheit: Immer Parameterisierung verwenden
  4. Validierung: Queries sollten validiert werden (z.B. nur SELECT erlauben)

Beispiel: Beide Interfaces zusammen nutzen

from modules.interfaces.interfaceDbRealEstateChatObjects import getInterface as getChatInterface
from modules.interfaces.interfaceDbRealEstateObjects import getInterface as getRealEstateInterface

# CRUD-Interface für strukturierte Operationen
realEstateInterface = getRealEstateInterface(currentUser)
projekt = realEstateInterface.createProjekt(Projekt(
    mandateId=currentUser.mandateId,
    label="Neues Projekt"
))

# Query-Interface für komplexe SELECT-Queries (optional)
chatInterface = getChatInterface(currentUser)
results = chatInterface.executeQuery(
    "SELECT p.*, COUNT(parz.id) as parzellen_count FROM Projekt p LEFT JOIN Parzelle parz ON parz.projektId = p.id GROUP BY p.id"
)

Zusammenfassung: Benötigte Dateien

Erforderlich (für CRUD-Operationen):

  1. modules/datamodels/datamodelRealEstate.py

    • Real Estate-Datenmodelle (Projekt, Parzelle, Dokument, etc.)
  2. modules/interfaces/interfaceDbRealEstateAccess.py

    • Zugriffskontrolle für Real Estate-Entitäten (RealEstateAccess)
  3. modules/interfaces/interfaceDbRealEstateObjects.py

    • Real Estate CRUD-Interface (RealEstateObjects)
    • Nutzt interfaceDbRealEstateAccess.py
    • Haupt-Interface für alle CRUD-Operationen

Optional (für direkte SQL-Queries):

  1. ⚠️ modules/interfaces/interfaceDbRealEstateChatObjects.py
    • Query-Interface für direkte SQL-Ausführung (RealEstateChatObjects)
    • Stateless, keine Session-Management
    • Nur wenn Sie komplexe SELECT-Queries benötigen

← Zurück: Datenmodell erstellen | Weiter: Feature-Logik implementieren →