import db def ensure_flow_schema(slug): conn = db.connect(slug) conn.executescript( """ CREATE TABLE IF NOT EXISTS _flow_nodes ( id INTEGER PRIMARY KEY AUTOINCREMENT, screen_id INTEGER NOT NULL REFERENCES _screens(id) ON DELETE CASCADE, node_type TEXT NOT NULL, x REAL NOT NULL DEFAULT 40, y REAL NOT NULL DEFAULT 40, trigger_element_id INTEGER REFERENCES _screen_elements(id), trigger_event TEXT, cond_definition_id INTEGER, cond_row_id INTEGER, cond_field TEXT, cond_field_type TEXT, cond_operator TEXT, cond_value TEXT, action_type TEXT, target_screen_id INTEGER REFERENCES _screens(id), target_element_id INTEGER REFERENCES _screen_elements(id), element_property TEXT, element_value TEXT, target_definition_id INTEGER, target_row_id INTEGER, target_field TEXT, data_operation TEXT, data_value TEXT ); CREATE TABLE IF NOT EXISTS _flow_edges ( id INTEGER PRIMARY KEY AUTOINCREMENT, screen_id INTEGER NOT NULL REFERENCES _screens(id) ON DELETE CASCADE, from_node_id INTEGER NOT NULL REFERENCES _flow_nodes(id) ON DELETE CASCADE, from_port TEXT NOT NULL DEFAULT 'out', to_node_id INTEGER NOT NULL REFERENCES _flow_nodes(id) ON DELETE CASCADE ); """ ) # 2.4 — conditions combinées (ET/OU) : un nœud Condition peut tester # PLUSIEURS champs à la fois plutôt qu'un seul. cond_clauses (JSON, une # liste de clauses en plus de la clause "historique" cond_definition_id/ # cond_field/etc.) + cond_combinator ('et'/'ou') sont ajoutés en ALTER # TABLE pour ne rien casser sur les jeux déjà créés — un nœud sans # cond_clauses garde exactement son comportement d'avant (une seule # comparaison), voir evaluateConditionNode() dans templates/play.html. cols = {r["name"] for r in conn.execute("PRAGMA table_info(_flow_nodes)").fetchall()} if "cond_clauses" not in cols: conn.execute("ALTER TABLE _flow_nodes ADD COLUMN cond_clauses TEXT") if "cond_combinator" not in cols: conn.execute("ALTER TABLE _flow_nodes ADD COLUMN cond_combinator TEXT NOT NULL DEFAULT 'et'") if "target_variable" not in cols: # Action "Modifier une variable" (voir apply_variable_action.py) — # cible une variable globale par NOM plutôt qu'un champ d'objet ; # réutilise data_operation/data_value, déjà là pour "modifier_donnee". conn.execute("ALTER TABLE _flow_nodes ADD COLUMN target_variable TEXT") if "cond_source" not in cols: # Un nœud Condition peut désormais tester soit un champ d'objet # (comportement historique, cond_source absent ou "objet"), soit # une VARIABLE GLOBALE (cond_source="variable" — cond_variable : # son nom ; cond_variable_chemin : chemin optionnel ".champ"/ # "[index]" si elle est de type objet/tableau, même syntaxe que # "Chemin dans la variable" de la Condition de visibilité — voir # visibility_condition_controls.py). Chaque clause de cond_clauses # (JSON) porte les mêmes 3 clés en plus des siennes. Évalué côté # CLIENT (templates/play.html, evaluateConditionClause) contre # gameData.variables, exposé par full_game_payload.py. conn.execute("ALTER TABLE _flow_nodes ADD COLUMN cond_source TEXT") if "cond_variable" not in cols: conn.execute("ALTER TABLE _flow_nodes ADD COLUMN cond_variable TEXT") if "cond_variable_chemin" not in cols: conn.execute("ALTER TABLE _flow_nodes ADD COLUMN cond_variable_chemin TEXT") if "trigger_custom_event_id" not in cols: # Événements personnalisés (voir screens/custom_events/) : quel # événement un nœud Déclencheur écoute (trigger_event="evenement") # ou un nœud Action déclenche (action_type="declencher_evenement"). # Pas de FK vers _custom_events : une suppression d'événement # nettoie déjà ces colonnes elle-même (voir # screens/custom_events/delete_custom_event.py), une contrainte # ferait double emploi sans rien apporter de plus. conn.execute("ALTER TABLE _flow_nodes ADD COLUMN trigger_custom_event_id INTEGER") if "target_custom_event_id" not in cols: conn.execute("ALTER TABLE _flow_nodes ADD COLUMN target_custom_event_id INTEGER") conn.commit() conn.close()