Retire l'export exécutable (.exe) et la partie publique en ligne (/jouer)
Build and deploy / test-python (push) Successful in 7m5s
Build and deploy / test-js (push) Successful in 58s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped

Décision produit : seul l'export Web/SCORM (LMS) est pertinent — l'export
exécutable Windows autonome (publish/build_package.py, bouton "Publier")
et la partie publique par joueur (/jouer/<slug>, routes/public_play/,
"Publier en ligne") sont jugés redondants et retirés.

Conserve le mécanisme d'état "par joueur" (db/global_vars,
db/rows, per_player) : infrastructure générique déjà utilisée par
Score/Progression et testée indépendamment de toute route publique
(voir tests/test_player_state.py), aucune raison de la retirer.

_STATIC_ITEMS/_copy_characters (copie sélective des sprites CraftPix
réellement utilisés) migrent de build_package.py vers
build_scorm_package.py, seul appelant restant.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-09-02 10:59:28 +02:00
co-authored by Claude Sonnet 5
parent 82d00b3800
commit e82eb7ce88
37 changed files with 115 additions and 1106 deletions
+1 -1
View File
@@ -1 +1 @@
from . import auth, games, objects, screens, elements, legacy_actions, flow, element_types, uploads, play, animations, global_vars, publish, custom_events, flow_blocks, public_play, scenes, onboarding
from . import auth, games, objects, screens, elements, legacy_actions, flow, element_types, uploads, play, animations, global_vars, publish, custom_events, flow_blocks, scenes, onboarding
+1 -1
View File
@@ -1 +1 @@
from . import index, games_new, game_dashboard, game_rename, game_delete, game_toggle_public_played, game_scoring_view
from . import index, games_new, game_dashboard, game_rename, game_delete, game_scoring_view
-16
View File
@@ -1,16 +0,0 @@
from flask import request, redirect, url_for
import db
from core.flask_app import app
@app.route("/game/<slug>/toggle-public-played", methods=["POST"])
def game_toggle_public_played(slug):
"""Bascule "Publier en ligne" (voir db/games/is_public_played.py) —
active/désactive la route publique /jouer/<slug> (routes/public_play/),
jouable par n'importe quel visiteur anonyme avec sa propre partie
(état par joueur, Phase 1). Distinct de "Publier" (export .zip
exécutable mono-joueur, voir routes/publish/publish_game.py)."""
db.set_public_played(slug, request.form.get("value") == "1")
return redirect(request.referrer or url_for("game_dashboard", slug=slug))
-9
View File
@@ -1,9 +0,0 @@
from . import (
game_play_public,
runtime_payload_public,
flow_node_run_data_public,
flow_node_run_variable_public,
flow_node_run_score_public,
flow_node_run_status_public,
flow_node_run_add_row_public,
)
@@ -1,25 +0,0 @@
from flask import jsonify, abort
import db
import screens
from core.flask_app import app
from core.player_identity import get_or_create_player_id, set_player_id_cookie
@app.route("/jouer/<slug>/flow/nodes/<int:node_id>/run-add-row", methods=["POST"])
def flow_node_run_add_row_public(slug, node_id):
"""Miroir public de routes/flow/flow_node_run_add_row.py (aperçu
créateur) — voir routes/public_play/game_play_public.py pour le
principe général."""
if not db.is_public_played(slug):
abort(404)
player_id, is_new = get_or_create_player_id()
node = screens.get_flow_node(slug, node_id)
if not node or node.get("node_type") != "action" or node.get("action_type") != "ajouter_ligne":
return jsonify({"ok": False}), 400
row_id = screens.apply_add_row_action(slug, node, player_id)
resp = app.make_response(jsonify({"ok": row_id is not None, "row_id": row_id}))
if is_new:
set_player_id_cookie(resp, player_id)
return resp
@@ -1,33 +0,0 @@
from flask import jsonify, request, abort
import db
import screens
from core.flask_app import app
from core.player_identity import get_or_create_player_id, set_player_id_cookie
@app.route("/jouer/<slug>/flow/nodes/<int:node_id>/run-data", methods=["POST"])
def flow_node_run_data_public(slug, node_id):
"""Miroir public de routes/flow/flow_node_run_data.py (aperçu créateur)
— voir routes/public_play/game_play_public.py pour le principe
général. `player_id` (le cookie du visiteur) est passé à
apply_data_action pour qu'un objet per_player agisse sur SA ligne."""
if not db.is_public_played(slug):
abort(404)
player_id, is_new = get_or_create_player_id()
node = screens.get_flow_node(slug, node_id)
if not node or node.get("node_type") != "action" or node.get("action_type") != "modifier_donnee":
return jsonify({"ok": False}), 400
if node.get("target_row_id") == screens.CLICKED_ROW_ID:
body = request.get_json(silent=True) or {}
node = {**node, "target_row_id": body.get("clicked_row_id")}
elif node.get("target_row_id") == screens.LAST_INSERTED_ROW_ID:
# Phase 4 — voir routes/flow/flow_node_run_data.py.
body = request.get_json(silent=True) or {}
node = {**node, "target_row_id": body.get("last_inserted_row_id")}
ok = screens.apply_data_action(slug, node, player_id)
resp = app.make_response(jsonify({"ok": ok}))
if is_new:
set_player_id_cookie(resp, player_id)
return resp
@@ -1,25 +0,0 @@
from flask import jsonify, abort
import db
import screens
from core.flask_app import app
from core.player_identity import get_or_create_player_id, set_player_id_cookie
@app.route("/jouer/<slug>/flow/nodes/<int:node_id>/run-score", methods=["POST"])
def flow_node_run_score_public(slug, node_id):
"""Miroir public de routes/flow/flow_node_run_score.py (aperçu
créateur) — voir routes/public_play/game_play_public.py pour le
principe général."""
if not db.is_public_played(slug):
abort(404)
player_id, is_new = get_or_create_player_id()
node = screens.get_flow_node(slug, node_id)
if not node or node.get("node_type") != "action" or node.get("action_type") != "modifier_score":
return jsonify({"ok": False}), 400
ok = screens.apply_score_action(slug, node, player_id)
resp = app.make_response(jsonify({"ok": ok}))
if is_new:
set_player_id_cookie(resp, player_id)
return resp
@@ -1,25 +0,0 @@
from flask import jsonify, abort
import db
import screens
from core.flask_app import app
from core.player_identity import get_or_create_player_id, set_player_id_cookie
@app.route("/jouer/<slug>/flow/nodes/<int:node_id>/run-status", methods=["POST"])
def flow_node_run_status_public(slug, node_id):
"""Miroir public de routes/flow/flow_node_run_status.py (aperçu
créateur) — voir routes/public_play/game_play_public.py pour le
principe général."""
if not db.is_public_played(slug):
abort(404)
player_id, is_new = get_or_create_player_id()
node = screens.get_flow_node(slug, node_id)
if not node or node.get("node_type") != "action" or node.get("action_type") != "definir_statut_partie":
return jsonify({"ok": False}), 400
ok = screens.apply_status_action(slug, node, player_id)
resp = app.make_response(jsonify({"ok": ok}))
if is_new:
set_player_id_cookie(resp, player_id)
return resp
@@ -1,25 +0,0 @@
from flask import jsonify, abort
import db
import screens
from core.flask_app import app
from core.player_identity import get_or_create_player_id, set_player_id_cookie
@app.route("/jouer/<slug>/flow/nodes/<int:node_id>/run-variable", methods=["POST"])
def flow_node_run_variable_public(slug, node_id):
"""Miroir public de routes/flow/flow_node_run_variable.py (aperçu
créateur) — voir routes/public_play/game_play_public.py pour le
principe général."""
if not db.is_public_played(slug):
abort(404)
player_id, is_new = get_or_create_player_id()
node = screens.get_flow_node(slug, node_id)
if not node or node.get("node_type") != "action" or node.get("action_type") != "modifier_variable":
return jsonify({"ok": False}), 400
ok = screens.apply_variable_action(slug, node, player_id)
resp = app.make_response(jsonify({"ok": ok}))
if is_new:
set_player_id_cookie(resp, player_id)
return resp
-40
View File
@@ -1,40 +0,0 @@
import json
from flask import render_template, url_for, abort
import db
import screens
from core.flask_app import app
from core.player_identity import get_or_create_player_id, set_player_id_cookie
@app.route("/jouer/<slug>")
def game_play_public(slug):
"""Route PUBLIQUE, non authentifiée (voir core/auth_guard.py) —
distincte de /game/<slug>/play (aperçu créateur, connecté, INCHANGÉE) :
un vrai visiteur anonyme, identifié par un cookie (voir
core/player_identity.py), avec sa propre partie (variables/objets
per_player, voir db/global_vars/, db/rows/). 404 si le créateur n'a
pas explicitement basculé "Publier en ligne" dans son tableau de bord
— jamais exposé par défaut."""
if not db.is_public_played(slug):
abort(404)
game = db.game_meta(slug)
player_id, is_new = get_or_create_player_id()
payload = screens.full_game_payload(slug, player_id)
resp = app.make_response(
render_template(
"play.html", game=game, screens_data=payload["screens"], payload_json=json.dumps(payload),
asset_url=lambda filename: url_for("static", filename=filename),
runtime_payload_url=url_for("runtime_payload_public", slug=slug),
flow_node_run_data_url=url_for("flow_node_run_data_public", slug=slug, node_id=999999),
flow_node_run_variable_url=url_for("flow_node_run_variable_public", slug=slug, node_id=999999),
flow_node_run_score_url=url_for("flow_node_run_score_public", slug=slug, node_id=999999),
flow_node_run_status_url=url_for("flow_node_run_status_public", slug=slug, node_id=999999),
flow_node_run_add_row_url=url_for("flow_node_run_add_row_public", slug=slug, node_id=999999),
)
)
if is_new:
set_player_id_cookie(resp, player_id)
return resp
@@ -1,20 +0,0 @@
from flask import jsonify, abort
import db
import screens
from core.flask_app import app
from core.player_identity import get_or_create_player_id, set_player_id_cookie
@app.route("/jouer/<slug>/runtime-payload")
def runtime_payload_public(slug):
"""Miroir public de routes/flow/runtime_payload.py (aperçu créateur) —
voir routes/public_play/game_play_public.py pour le principe général."""
if not db.is_public_played(slug):
abort(404)
player_id, is_new = get_or_create_player_id()
resp = app.make_response(jsonify(screens.full_game_payload(slug, player_id)))
if is_new:
set_player_id_cookie(resp, player_id)
return resp
+1 -1
View File
@@ -1 +1 @@
from . import publish_game, export_scorm
from . import export_scorm
-28
View File
@@ -1,28 +0,0 @@
import os
from flask import after_this_request, send_file
import db
from publish.build_package import build_game_zip
from core.flask_app import app
@app.route("/game/<slug>/publish", methods=["POST"])
def publish_game(slug):
"""Construit et renvoie le zip exécutable autonome du jeu (voir
publish/build_package.py) — protégée par la même garde d'accès que
toutes les autres routes /game/<slug>/... (core/auth_guard.py),
aucune vérification supplémentaire nécessaire ici."""
zip_path, game_name = build_game_zip(slug)
@after_this_request
def _cleanup(response):
try:
os.remove(zip_path)
except OSError:
pass
return response
download_name = f"{db.slugify(game_name)}.zip"
return send_file(zip_path, as_attachment=True, download_name=download_name, mimetype="application/zip")