fix(data): supprimer un compte emporte ses contrats
DATA-012. delete_user supprimait les lignes Contract et laissait les PDF. Des contrats nominatifs signes restaient donc sur le serveur apres la suppression du compte, sans plus aucune reference en base : invisibles pour l application, ingerables par elle, et toujours des donnees personnelles. Les chemins sont lus **avant** que les lignes partent — apres, plus rien ne dit ou sont les fichiers — et les fichiers sont retires **apres** le commit. L ordre compte dans ce sens et pas dans l autre : un echec entre les deux doit laisser un fichier sans ligne, ce qui est recuperable et correspond exactement a l etat precedent, plutot qu une ligne sans fichier, qui est un telechargement en 500 pour toujours. Un fichier deja absent est journalise en info et ignore ; un fichier impossible a retirer est journalise en erreur avec ce que ca implique — il devient orphelin, donc plus rien dans l application ne proposera jamais de le supprimer. Rien ici ne peut faire echouer la suppression du compte : le compte est la partie que quelqu un a demandee. Le nombre de fichiers retires part dans le journal d authentification, a cote de account.deleted. 554 tests.
This commit is contained in:
@@ -42,6 +42,7 @@ from app.routes.users._shared import (
|
||||
update_user_gamertags,
|
||||
)
|
||||
from app.routes.users.blueprint import users_bp
|
||||
from app.storage import discard_documents
|
||||
from app.validators import CreateUserSchema, EditUserSchema
|
||||
|
||||
|
||||
@@ -249,6 +250,15 @@ def delete_user(user_id):
|
||||
db.or_(OneOnOneRequest.player_id == user_id, OneOnOneRequest.coach_id == user_id),
|
||||
).delete(synchronize_session=False)
|
||||
UserGamertag.query.filter_by(user_id=user_id).delete()
|
||||
|
||||
# Read the file paths before the rows go: afterwards there is nothing
|
||||
# left to say where the PDFs are (DATA-012). The files themselves are
|
||||
# removed after the commit, below.
|
||||
contract_files = [
|
||||
path
|
||||
for contract in Contract.query.filter_by(player_id=user_id).all()
|
||||
for path in (contract.file_path, contract.signed_file_path)
|
||||
]
|
||||
Contract.query.filter_by(player_id=user_id).delete()
|
||||
TryoutRegistration.query.filter_by(player_id=user_id).delete()
|
||||
TeamPlayer.query.filter_by(player_id=user_id).delete()
|
||||
@@ -265,6 +275,12 @@ def delete_user(user_id):
|
||||
deleted_username, deleted_role = user.username, user.role
|
||||
db.session.delete(user)
|
||||
db.session.commit()
|
||||
|
||||
# After the commit, deliberately. A failure here leaves a file with no
|
||||
# row — recoverable, and exactly what happened before this existed —
|
||||
# rather than a row with no file, which is a download that 500s for ever.
|
||||
discarded = discard_documents(contract_files)
|
||||
|
||||
log_auth_event(
|
||||
'account.deleted',
|
||||
actor=current_user.username,
|
||||
@@ -272,6 +288,7 @@ def delete_user(user_id):
|
||||
target=deleted_username,
|
||||
target_id=user_id,
|
||||
role=deleted_role,
|
||||
contract_files_removed=discarded,
|
||||
)
|
||||
flash(
|
||||
_('User %(deleted_username)s has been removed.', deleted_username=deleted_username),
|
||||
|
||||
Reference in New Issue
Block a user