Structure de dossiers par utilisateur : projects/<propriétaire>/<projet>/
Remplace le rangement plat projects/<slug>/ par une vraie arborescence par compte créateur, sans toucher aux routes existantes (aucune ne déclare de "/" dans son <slug> : le chemin composé "propriétaire_projet" reste un détail interne à db/, décodé par db/games/project_slug.py, seul fichier à modifier si la convention change un jour). - db/game_dir.py, create_game.py, delete_game.py, list_games.py, move_game.py : résolvent/construisent ce chemin composé. Bénéfice direct : deux comptes différents peuvent chacun avoir un projet nommé pareil sans collision (l'unicité ne se vérifie plus que par dossier propriétaire). move_game.py renomme désormais le dossier PROPRIÉTAIRE entier (changement d'email), prêt pour un futur multi-projet. - scripts/migrate_flat_project_slugs.py : migration des projets déjà créés sous l'ancien rangement plat (simulation par défaut, --apply pour exécuter). - routes/auth/profile.py, tests/conftest.py : suppression de compte/jeu de test via db.delete_game (nettoie aussi le dossier propriétaire devenu vide) plutôt qu'un shutil.rmtree direct du seul dossier projet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
d5a59bdbd8
commit
0f23024882
@@ -1,7 +1,15 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from .constants import PROJECTS_DIR
|
from .constants import PROJECTS_DIR
|
||||||
|
from .games.project_slug import split_slug
|
||||||
|
|
||||||
|
|
||||||
def game_dir(slug):
|
def game_dir(slug):
|
||||||
|
# Structure de dossiers par utilisateur (voir db/games/project_slug.py)
|
||||||
|
# : un slug composé "propriétaire_projet" résout vers un vrai chemin
|
||||||
|
# imbriqué projects/<propriétaire>/<projet>/ — repli sur l'ancien
|
||||||
|
# comportement plat pour un slug pas encore migré (project_part=None).
|
||||||
|
owner_folder, project_part = split_slug(slug)
|
||||||
|
if project_part is None:
|
||||||
return os.path.join(PROJECTS_DIR, slug)
|
return os.path.join(PROJECTS_DIR, slug)
|
||||||
|
return os.path.join(PROJECTS_DIR, owner_folder, project_part)
|
||||||
|
|||||||
+29
-17
@@ -3,30 +3,43 @@ import os
|
|||||||
from ..connection import connect
|
from ..connection import connect
|
||||||
from ..game_dir import game_dir
|
from ..game_dir import game_dir
|
||||||
from ..slugify import slugify
|
from ..slugify import slugify
|
||||||
from .get_game_type import DEFAULT_GAME_TYPE
|
from .project_slug import build_slug
|
||||||
|
|
||||||
|
|
||||||
def create_game(name, slug_override=None, game_type=DEFAULT_GAME_TYPE):
|
def create_game(name, owner_folder=None, project_slug_override=None):
|
||||||
"""Feature 1 : crée le dossier du jeu, ses fichiers index.html/css/js
|
"""Feature 1 : crée le dossier du jeu, ses fichiers index.html/css/js
|
||||||
reliés entre eux, et sa base de données dédiée (nom du jeu en méta).
|
reliés entre eux, et sa base de données dédiée (nom du jeu en méta).
|
||||||
|
|
||||||
slug_override : impose le nom du dossier au lieu de le dériver de
|
owner_folder : dossier PROPRIÉTAIRE (voir db/games/project_slug.py —
|
||||||
`name` — utilisé par l'inscription (auth/) pour que le dossier d'un
|
structure de dossiers par utilisateur), typiquement
|
||||||
compte porte son adresse email (slugifiée), indépendamment du nom
|
slugify(email_du_compte) — le slug final composé
|
||||||
donné à son jeu. Toujours rendu unique de la même façon (suffixe
|
"<owner_folder>_<project_part>" résout vers un vrai chemin imbriqué
|
||||||
-2/-3...) si, par un hasard extrême, le dossier existe déjà.
|
projects/<owner_folder>/<project_part>/. Omis (None) : repli sur
|
||||||
|
l'ancien comportement plat (slug à un seul segment, projects/<slug>/)
|
||||||
|
— utile pour les tests de la couche données qui n'ont pas de compte
|
||||||
|
utilisateur à représenter.
|
||||||
|
|
||||||
game_type : "document" (défaut, éditeur générique actuel) ou "jeu_2d"
|
project_slug_override : impose le nom du dossier PROJET au lieu de le
|
||||||
(éditeur de scène — voir get_game_type.py) — choisi une fois pour
|
dériver de `name` — utilisé par l'inscription (auth/, voir
|
||||||
toutes à la création, gate quel éditeur/quel rendu jouable ce jeu
|
routes/onboarding/) pour que le tout premier projet d'un compte porte
|
||||||
utilise (voir routes/screens/screen_edit.py)."""
|
un nom de dossier prévisible. Le suffixe d'unicité (-2/-3...) ne
|
||||||
slug = slugify(slug_override) if slug_override else slugify(name)
|
compare que parmi les projets déjà présents dans CE `owner_folder`
|
||||||
base = slug
|
(deux comptes différents peuvent donc avoir chacun un projet nommé
|
||||||
|
pareil, sans collision).
|
||||||
|
|
||||||
|
Un projet n'a plus de "type" propre (voir screens/screens_repo/
|
||||||
|
ensure_schema.py — fusion des moteurs, "document"/"jeu_2d" est
|
||||||
|
désormais une propriété PAR ÉCRAN) : un projet fraîchement créé n'a
|
||||||
|
par défaut aucun écran, c'est à l'appelant d'en créer un premier avec
|
||||||
|
le "kind" voulu (voir screens.create_screen)."""
|
||||||
|
project_part = slugify(project_slug_override) if project_slug_override else slugify(name)
|
||||||
|
base = project_part
|
||||||
n = 2
|
n = 2
|
||||||
while os.path.isdir(game_dir(base)):
|
while os.path.isdir(game_dir(build_slug(owner_folder, base) if owner_folder else base)):
|
||||||
base = f"{slug}-{n}"
|
base = f"{project_part}-{n}"
|
||||||
n += 1
|
n += 1
|
||||||
slug = base
|
project_part = base
|
||||||
|
slug = build_slug(owner_folder, project_part) if owner_folder else project_part
|
||||||
|
|
||||||
os.makedirs(game_dir(slug), exist_ok=True)
|
os.makedirs(game_dir(slug), exist_ok=True)
|
||||||
|
|
||||||
@@ -84,7 +97,6 @@ console.log("Jeu chargé.");
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
conn.execute("INSERT INTO _meta (key, value) VALUES ('name', ?)", (name,))
|
conn.execute("INSERT INTO _meta (key, value) VALUES ('name', ?)", (name,))
|
||||||
conn.execute("INSERT INTO _meta (key, value) VALUES ('game_type', ?)", (game_type,))
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
return slug
|
return slug
|
||||||
|
|||||||
@@ -1,7 +1,20 @@
|
|||||||
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
|
from ..constants import PROJECTS_DIR
|
||||||
from ..game_dir import game_dir
|
from ..game_dir import game_dir
|
||||||
|
from .project_slug import split_slug
|
||||||
|
|
||||||
|
|
||||||
def delete_game(slug):
|
def delete_game(slug):
|
||||||
shutil.rmtree(game_dir(slug))
|
shutil.rmtree(game_dir(slug))
|
||||||
|
# Structure de dossiers par utilisateur (voir project_slug.py) :
|
||||||
|
# nettoie aussi le dossier propriétaire s'il ne contient plus aucun
|
||||||
|
# projet — purement cosmétique (game_dir()/list_games() fonctionnent
|
||||||
|
# très bien avec un dossier propriétaire vide), évite juste
|
||||||
|
# d'accumuler des dossiers fantômes sous projects/.
|
||||||
|
owner_folder, project_part = split_slug(slug)
|
||||||
|
if project_part is not None:
|
||||||
|
owner_path = os.path.join(PROJECTS_DIR, owner_folder)
|
||||||
|
if os.path.isdir(owner_path) and not os.listdir(owner_path):
|
||||||
|
os.rmdir(owner_path)
|
||||||
|
|||||||
+22
-5
@@ -3,18 +3,35 @@ import os
|
|||||||
from ..constants import PROJECTS_DIR
|
from ..constants import PROJECTS_DIR
|
||||||
from ..connection import connect
|
from ..connection import connect
|
||||||
from ..db_path import db_path
|
from ..db_path import db_path
|
||||||
|
from .project_slug import build_slug
|
||||||
|
|
||||||
|
|
||||||
def list_games():
|
def list_games():
|
||||||
"""Scanne le dossier projects/ : chaque sous-dossier avec un game.db est un jeu."""
|
"""Scanne projects/ : structure par utilisateur (voir
|
||||||
|
db/games/project_slug.py) — projects/<propriétaire>/<projet>/game.db,
|
||||||
|
deux niveaux — plus le repli sur l'ancien rangement plat
|
||||||
|
projects/<slug>/game.db pour tout slug pas encore migré (voir
|
||||||
|
scripts/migrate_flat_project_slugs.py)."""
|
||||||
games = []
|
games = []
|
||||||
if not os.path.isdir(PROJECTS_DIR):
|
if not os.path.isdir(PROJECTS_DIR):
|
||||||
return games
|
return games
|
||||||
for slug in sorted(os.listdir(PROJECTS_DIR)):
|
for entry in sorted(os.listdir(PROJECTS_DIR)):
|
||||||
path = db_path(slug)
|
entry_path = os.path.join(PROJECTS_DIR, entry)
|
||||||
if os.path.isfile(path):
|
if not os.path.isdir(entry_path):
|
||||||
|
continue
|
||||||
|
if os.path.isfile(os.path.join(entry_path, "game.db")):
|
||||||
|
# Repli : un ancien dossier plat porte directement game.db.
|
||||||
|
_append_game(games, entry)
|
||||||
|
continue
|
||||||
|
for project_part in sorted(os.listdir(entry_path)):
|
||||||
|
slug = build_slug(entry, project_part)
|
||||||
|
if os.path.isfile(db_path(slug)):
|
||||||
|
_append_game(games, slug)
|
||||||
|
return games
|
||||||
|
|
||||||
|
|
||||||
|
def _append_game(games, slug):
|
||||||
conn = connect(slug)
|
conn = connect(slug)
|
||||||
row = conn.execute("SELECT value FROM _meta WHERE key = 'name'").fetchone()
|
row = conn.execute("SELECT value FROM _meta WHERE key = 'name'").fetchone()
|
||||||
conn.close()
|
conn.close()
|
||||||
games.append({"slug": slug, "name": row["value"] if row else slug})
|
games.append({"slug": slug, "name": row["value"] if row else slug})
|
||||||
return games
|
|
||||||
|
|||||||
+33
-13
@@ -1,23 +1,43 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
|
from ..constants import PROJECTS_DIR
|
||||||
from ..game_dir import game_dir
|
from ..game_dir import game_dir
|
||||||
|
from .project_slug import build_slug, split_slug
|
||||||
|
|
||||||
|
|
||||||
def move_game(old_slug, new_slug):
|
def move_game(old_slug, new_owner_folder):
|
||||||
"""Renomme le dossier d'un jeu vers un nouveau slug — utilisé quand un
|
"""Renomme le dossier PROPRIÉTAIRE d'un compte — utilisé quand un
|
||||||
utilisateur change son adresse email (voir routes/auth/profile.py,
|
utilisateur change son adresse email (voir routes/auth/profile.py,
|
||||||
project_slug = slugify(email) pour un compte "user") : le dossier
|
owner_folder = slugify(email) pour un compte "user", voir
|
||||||
physique du jeu doit suivre. Rend le nouveau slug unique de la même
|
db/games/project_slug.py) : le dossier physique doit suivre. Renomme
|
||||||
façon que create_game() si, par un hasard extrême, il correspond déjà
|
le dossier propriétaire ENTIER en un coup (déplace tous les projets de
|
||||||
à un dossier existant. Renvoie le slug FINAL (potentiellement suffixé),
|
ce compte ensemble — prêt pour un futur multi-projet, même si un seul
|
||||||
à enregistrer comme nouveau project_slug."""
|
aujourd'hui), pas juste un projet. Rend le nouveau dossier propriétaire
|
||||||
if old_slug == new_slug:
|
unique de la même façon que create_game() si, par un hasard extrême,
|
||||||
|
il correspond déjà à un dossier existant. Renvoie le nouveau slug
|
||||||
|
composé FINAL (même projet, propriétaire renommé), à enregistrer comme
|
||||||
|
nouveau project_slug."""
|
||||||
|
old_owner_folder, project_part = split_slug(old_slug)
|
||||||
|
if project_part is None:
|
||||||
|
# Slug pas encore migré vers la structure par utilisateur (voir
|
||||||
|
# scripts/migrate_flat_project_slugs.py) — repli sur l'ancien
|
||||||
|
# comportement plat (renomme le dossier du projet lui-même).
|
||||||
|
if old_slug == new_owner_folder:
|
||||||
return old_slug
|
return old_slug
|
||||||
base = new_slug
|
base = new_owner_folder
|
||||||
n = 2
|
n = 2
|
||||||
while os.path.isdir(game_dir(base)):
|
while os.path.isdir(game_dir(base)):
|
||||||
base = f"{new_slug}-{n}"
|
base = f"{new_owner_folder}-{n}"
|
||||||
n += 1
|
n += 1
|
||||||
new_slug = base
|
os.rename(game_dir(old_slug), game_dir(base))
|
||||||
os.rename(game_dir(old_slug), game_dir(new_slug))
|
return base
|
||||||
return new_slug
|
|
||||||
|
if old_owner_folder == new_owner_folder:
|
||||||
|
return old_slug
|
||||||
|
base = new_owner_folder
|
||||||
|
n = 2
|
||||||
|
while os.path.isdir(os.path.join(PROJECTS_DIR, base)):
|
||||||
|
base = f"{new_owner_folder}-{n}"
|
||||||
|
n += 1
|
||||||
|
os.rename(os.path.join(PROJECTS_DIR, old_owner_folder), os.path.join(PROJECTS_DIR, base))
|
||||||
|
return build_slug(base, project_part)
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
"""Convention du slug composé "propriétaire_projet" (voir le plan
|
||||||
|
"Structure de dossiers propriétaire/projet") : un slug encode en réalité
|
||||||
|
deux segments — le dossier PROPRIÉTAIRE (slugifié depuis l'email du
|
||||||
|
compte qui a créé le projet) et le dossier PROJET à l'intérieur — pour
|
||||||
|
que projects/ soit une vraie arborescence par utilisateur
|
||||||
|
(projects/<propriétaire>/<projet>/), sans jamais toucher aux ~40 fichiers
|
||||||
|
de routes qui déclarent <slug> dans leur URL : le slug reste, pour elles,
|
||||||
|
un seul segment plat et opaque.
|
||||||
|
|
||||||
|
"_" comme séparateur est sûr et non ambigu : slugify() (voir
|
||||||
|
db/slugify.py) ne produit jamais de "_", seulement [a-z0-9-]+ — un slug
|
||||||
|
composé peut donc toujours être découpé sans collision sur la PREMIÈRE
|
||||||
|
occurrence de "_" (le dossier propriétaire, lui, n'en contient jamais).
|
||||||
|
|
||||||
|
Seule une poignée de fonctions dans db/games/ (game_dir, create_game,
|
||||||
|
move_game, list_games) savent que le slug encode ce chemin composé — si
|
||||||
|
la convention change un jour, ce fichier est le seul à modifier."""
|
||||||
|
|
||||||
|
|
||||||
|
def build_slug(owner_folder, project_part):
|
||||||
|
return f"{owner_folder}_{project_part}"
|
||||||
|
|
||||||
|
|
||||||
|
def split_slug(slug):
|
||||||
|
"""(owner_folder, project_part) si `slug` est bien composé, sinon
|
||||||
|
(slug, None) — un slug "plat" (créé avant cette convention, pas encore
|
||||||
|
migré par scripts/migrate_flat_project_slugs.py) reste lisible tel
|
||||||
|
quel par le code appelant (repli sur l'ancien comportement)."""
|
||||||
|
owner_folder, sep, project_part = slug.partition("_")
|
||||||
|
if not sep:
|
||||||
|
return slug, None
|
||||||
|
return owner_folder, project_part
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import os
|
import os
|
||||||
import shutil
|
|
||||||
|
|
||||||
from flask import g, redirect, render_template, request, session, url_for
|
from flask import g, redirect, render_template, request, session, url_for
|
||||||
|
|
||||||
@@ -106,9 +105,12 @@ def profile_delete():
|
|||||||
# plusieurs jeux qui ne lui sont pas dédiés de la même façon : ses
|
# plusieurs jeux qui ne lui sont pas dédiés de la même façon : ses
|
||||||
# projets restent intacts.
|
# projets restent intacts.
|
||||||
if user["role"] != "admin" and user.get("project_slug"):
|
if user["role"] != "admin" and user.get("project_slug"):
|
||||||
game_dir = db.game_dir(user["project_slug"])
|
if os.path.isdir(db.game_dir(user["project_slug"])):
|
||||||
if os.path.isdir(game_dir):
|
# db.delete_game (voir db/games/project_slug.py — structure de
|
||||||
shutil.rmtree(game_dir)
|
# dossiers par utilisateur) nettoie aussi le dossier
|
||||||
|
# propriétaire s'il devient vide, contrairement à un simple
|
||||||
|
# shutil.rmtree du dossier projet seul.
|
||||||
|
db.delete_game(user["project_slug"])
|
||||||
|
|
||||||
auth.delete_user(user["id"])
|
auth.delete_user(user["id"])
|
||||||
session.clear()
|
session.clear()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from flask import request, redirect, url_for
|
from flask import g, request, redirect, url_for
|
||||||
|
|
||||||
import db
|
import db
|
||||||
|
|
||||||
@@ -10,8 +10,12 @@ def games_new():
|
|||||||
name = request.form.get("name", "").strip()
|
name = request.form.get("name", "").strip()
|
||||||
if not name:
|
if not name:
|
||||||
return redirect(url_for("index"))
|
return redirect(url_for("index"))
|
||||||
game_type = request.form.get("game_type", db.DEFAULT_GAME_TYPE)
|
# Fusion des moteurs (voir screens/screens_repo/ensure_schema.py) : un
|
||||||
if game_type not in ("document", "jeu_2d"):
|
# jeu n'a plus de type propre — aucun écran créé d'office ici, comme
|
||||||
game_type = db.DEFAULT_GAME_TYPE
|
# avant (l'admin en crée via "+ Nouvel écran" dans le dashboard, en
|
||||||
slug = db.create_game(name, game_type=game_type)
|
# choisissant "document" ou "jeu_2d" pour chacun).
|
||||||
|
# owner_folder (voir db/games/project_slug.py — structure de dossiers
|
||||||
|
# par utilisateur) : posé par core/auth_guard.py pour tout compte
|
||||||
|
# connecté, y compris l'admin qui utilise cette route.
|
||||||
|
slug = db.create_game(name, owner_folder=db.slugify(g.current_user["email"]))
|
||||||
return redirect(url_for("game_dashboard", slug=slug))
|
return redirect(url_for("game_dashboard", slug=slug))
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
"""Script à usage unique (PAS exécuté au runtime du moteur) : migre les
|
||||||
|
projets encore rangés à plat (projects/<slug>/game.db, l'ancien schéma)
|
||||||
|
vers la structure par utilisateur (projects/<owner_folder>/<project_part>/,
|
||||||
|
voir db/games/project_slug.py et le plan "Structure de dossiers
|
||||||
|
propriétaire/projet") — db/game_dir.py sait déjà lire les deux formes,
|
||||||
|
cette migration n'est donc pas bloquante pour faire tourner le moteur,
|
||||||
|
mais range enfin les dossiers comme voulu et met à jour project_slug (voir
|
||||||
|
auth/set_project_slug.py) pour les comptes concernés.
|
||||||
|
|
||||||
|
owner_folder = l'ancien slug plat lui-même : c'était déjà, historiquement,
|
||||||
|
slugify(email) pour le SEUL projet d'un compte "user" (voir l'ancienne
|
||||||
|
version de routes/auth/register_2fa.py, avant l'onboarding guidé) — donc
|
||||||
|
le bon dossier propriétaire, tel quel. project_part = slugify(nom du jeu
|
||||||
|
stocké en _meta), pour donner un vrai nom de dossier PROJET distinct.
|
||||||
|
|
||||||
|
Mode simulation par défaut (n'écrit rien, affiche ce qui serait fait) —
|
||||||
|
`--apply` requis pour exécuter réellement :
|
||||||
|
python scripts/migrate_flat_project_slugs.py # aperçu
|
||||||
|
python scripts/migrate_flat_project_slugs.py --apply # exécute
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
_BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
sys.path.insert(0, _BASE_DIR)
|
||||||
|
|
||||||
|
import db # noqa: E402
|
||||||
|
import auth # noqa: E402
|
||||||
|
from db.constants import PROJECTS_DIR # noqa: E402
|
||||||
|
from db.games.project_slug import build_slug # noqa: E402
|
||||||
|
from auth.connection import connect as connect_auth_db # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
def _flat_project_slugs():
|
||||||
|
"""Slugs plats trouvés directement sous projects/ (un dossier avec
|
||||||
|
game.db à la racine, pas dans un sous-dossier propriétaire)."""
|
||||||
|
if not os.path.isdir(PROJECTS_DIR):
|
||||||
|
return []
|
||||||
|
found = []
|
||||||
|
for entry in sorted(os.listdir(PROJECTS_DIR)):
|
||||||
|
entry_path = os.path.join(PROJECTS_DIR, entry)
|
||||||
|
if os.path.isfile(os.path.join(entry_path, "game.db")):
|
||||||
|
found.append(entry)
|
||||||
|
return found
|
||||||
|
|
||||||
|
|
||||||
|
def migrate(apply=False):
|
||||||
|
slugs = _flat_project_slugs()
|
||||||
|
if not slugs:
|
||||||
|
print("Aucun projet à plat trouvé — rien à migrer.")
|
||||||
|
return
|
||||||
|
conn = connect_auth_db()
|
||||||
|
for old_slug in slugs:
|
||||||
|
game_meta = db.game_meta(old_slug)
|
||||||
|
project_part = db.slugify(game_meta["name"]) or "jeu"
|
||||||
|
new_slug = build_slug(old_slug, project_part)
|
||||||
|
owning_users = [
|
||||||
|
dict(r) for r in
|
||||||
|
conn.execute("SELECT id, email FROM _users WHERE project_slug = ?", (old_slug,)).fetchall()
|
||||||
|
]
|
||||||
|
print(f"{old_slug!r} -> {new_slug!r}"
|
||||||
|
f" (comptes concernés : {[u['email'] for u in owning_users] or 'aucun'})")
|
||||||
|
if not apply:
|
||||||
|
continue
|
||||||
|
old_dir = os.path.join(PROJECTS_DIR, old_slug)
|
||||||
|
new_dir = os.path.join(PROJECTS_DIR, old_slug, project_part)
|
||||||
|
# Renommer EN PLACE (dossier propriétaire = ancien dossier projet,
|
||||||
|
# déplacé dans son propre sous-dossier) demanderait un dossier
|
||||||
|
# intermédiaire — plus sûr de passer par un nom temporaire.
|
||||||
|
tmp_dir = old_dir + ".migrating"
|
||||||
|
os.rename(old_dir, tmp_dir)
|
||||||
|
os.makedirs(os.path.dirname(new_dir), exist_ok=True)
|
||||||
|
os.rename(tmp_dir, new_dir)
|
||||||
|
for user in owning_users:
|
||||||
|
auth.set_project_slug(user["id"], new_slug)
|
||||||
|
conn.close()
|
||||||
|
if apply:
|
||||||
|
print(f"{len(slugs)} projet(s) migré(s).")
|
||||||
|
else:
|
||||||
|
print("Aperçu seulement — relance avec --apply pour exécuter.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
migrate(apply="--apply" in sys.argv)
|
||||||
+4
-6
@@ -109,9 +109,8 @@ def user_game(client, user_client):
|
|||||||
with user_client.session_transaction() as sess:
|
with user_client.session_transaction() as sess:
|
||||||
auth.set_project_slug(sess["user_id"], slug)
|
auth.set_project_slug(sess["user_id"], slug)
|
||||||
yield slug
|
yield slug
|
||||||
game_dir = db.game_dir(slug)
|
if os.path.isdir(db.game_dir(slug)):
|
||||||
if os.path.isdir(game_dir):
|
db.delete_game(slug)
|
||||||
shutil.rmtree(game_dir)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -122,9 +121,8 @@ def game(client):
|
|||||||
assert resp.status_code == 302
|
assert resp.status_code == 302
|
||||||
slug = resp.headers["Location"].rstrip("/").split("/")[-1]
|
slug = resp.headers["Location"].rstrip("/").split("/")[-1]
|
||||||
yield slug
|
yield slug
|
||||||
game_dir = db.game_dir(slug)
|
if os.path.isdir(db.game_dir(slug)):
|
||||||
if os.path.isdir(game_dir):
|
db.delete_game(slug)
|
||||||
shutil.rmtree(game_dir)
|
|
||||||
|
|
||||||
|
|
||||||
_STATIC_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "static", "js")
|
_STATIC_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "static", "js")
|
||||||
|
|||||||
Reference in New Issue
Block a user