fix(#143): masquer Supprimer quand un gestionnaire édite sa propre fiche
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
d6dac181d2
commit
6788351070
111
backend/scripts/create-gitea-issue-gestionnaire-self-delete.js
Normal file
111
backend/scripts/create-gitea-issue-gestionnaire-self-delete.js
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/**
|
||||||
|
* Crée l'issue Gitea — gestionnaire ne doit pas pouvoir se supprimer.
|
||||||
|
* Usage: node backend/scripts/create-gitea-issue-gestionnaire-self-delete.js
|
||||||
|
*/
|
||||||
|
const https = require('https');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const repoRoot = path.join(__dirname, '../..');
|
||||||
|
const MILESTONE_0_1_0 = 10;
|
||||||
|
|
||||||
|
let token = process.env.GITEA_TOKEN;
|
||||||
|
if (!token) {
|
||||||
|
try {
|
||||||
|
const tokenFile = path.join(repoRoot, '.gitea-token');
|
||||||
|
if (fs.existsSync(tokenFile)) token = fs.readFileSync(tokenFile, 'utf8').trim();
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
if (!token) {
|
||||||
|
try {
|
||||||
|
const briefing = fs.readFileSync(
|
||||||
|
path.join(repoRoot, 'docs/27_BRIEFING-FRONTEND.md'),
|
||||||
|
'utf8',
|
||||||
|
);
|
||||||
|
const m = briefing.match(/Token:\s*(gitebu_[a-f0-9]+)/);
|
||||||
|
if (m) token = m[1].trim();
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
if (!token) {
|
||||||
|
console.error('Token non trouvé : .gitea-token ou GITEA_TOKEN');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = `## Contexte
|
||||||
|
|
||||||
|
Quand un **gestionnaire** connecté ouvre sa propre fiche dans l'onglet **Gestionnaires** (Gestion des utilisateurs), la modale **Modifier un "Gestionnaire"** affiche le bouton **Supprimer**.
|
||||||
|
|
||||||
|
Comportement actuel : le gestionnaire voit et peut tenter de supprimer son propre compte.
|
||||||
|
|
||||||
|
## Comportement attendu
|
||||||
|
|
||||||
|
Comme pour le **super administrateur** (bouton Supprimer masqué sur la fiche super admin) :
|
||||||
|
|
||||||
|
- **Pas de bouton Supprimer** quand l'utilisateur édite **sa propre fiche**
|
||||||
|
- **Modification** des informations (prénom, nom, email, téléphone, relais, mot de passe) **toujours autorisée**
|
||||||
|
|
||||||
|
## Périmètre
|
||||||
|
|
||||||
|
- Frontend : \`AdminUserFormDialog\` (\`gestionnaires_create.dart\`)
|
||||||
|
- Comparer \`initialUser.id\` avec l'utilisateur connecté (\`AuthService.getCurrentUser\`)
|
||||||
|
- Masquer Supprimer si édition de soi-même ; conserver garde existante super admin
|
||||||
|
|
||||||
|
## Critères d'acceptation
|
||||||
|
|
||||||
|
- [ ] Gestionnaire connecté → ouvre sa fiche → **pas** de bouton Supprimer
|
||||||
|
- [ ] Gestionnaire connecté → peut **Modifier** ses informations
|
||||||
|
- [ ] Super admin / admin → peut toujours supprimer **un autre** gestionnaire (si droits API)
|
||||||
|
- [ ] Pas de régression sur fiche super administrateur (Supprimer toujours masqué)
|
||||||
|
|
||||||
|
## Fichiers clés
|
||||||
|
|
||||||
|
- \`frontend/lib/screens/administrateurs/creation/gestionnaires_create.dart\`
|
||||||
|
- \`frontend/lib/widgets/admin/gestionnaire_management_widget.dart\`
|
||||||
|
|
||||||
|
## Milestone
|
||||||
|
|
||||||
|
**0.1.0** — correction UX / sécurité gestion utilisateurs.`;
|
||||||
|
|
||||||
|
const payloadClean = JSON.stringify({
|
||||||
|
title: '[Bug] Gestionnaire peut voir Supprimer sur sa propre fiche',
|
||||||
|
body,
|
||||||
|
milestone: MILESTONE_0_1_0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const opts = {
|
||||||
|
hostname: 'git.ptits-pas.fr',
|
||||||
|
path: '/api/v1/repos/jmartin/petitspas/issues',
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: 'token ' + token,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Content-Length': Buffer.byteLength(payloadClean),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const req = https.request(opts, (res) => {
|
||||||
|
let d = '';
|
||||||
|
res.on('data', (c) => (d += c));
|
||||||
|
res.on('end', () => {
|
||||||
|
try {
|
||||||
|
const o = JSON.parse(d);
|
||||||
|
if (o.number) {
|
||||||
|
console.log('NUMBER:', o.number);
|
||||||
|
console.log('URL:', o.html_url);
|
||||||
|
console.log('MILESTONE:', o.milestone?.title ?? '(aucun)');
|
||||||
|
} else {
|
||||||
|
console.error('Réponse:', d);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(d);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
req.on('error', (e) => {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
req.write(payloadClean);
|
||||||
|
req.end();
|
||||||
@ -5,6 +5,7 @@ import 'package:p_tits_pas/utils/email_utils.dart';
|
|||||||
import 'package:p_tits_pas/widgets/email_text_field.dart';
|
import 'package:p_tits_pas/widgets/email_text_field.dart';
|
||||||
import 'package:p_tits_pas/widgets/french_phone_field.dart';
|
import 'package:p_tits_pas/widgets/french_phone_field.dart';
|
||||||
import 'package:p_tits_pas/models/user.dart';
|
import 'package:p_tits_pas/models/user.dart';
|
||||||
|
import 'package:p_tits_pas/services/auth_service.dart';
|
||||||
import 'package:p_tits_pas/services/relais_service.dart';
|
import 'package:p_tits_pas/services/relais_service.dart';
|
||||||
import 'package:p_tits_pas/services/user_service.dart';
|
import 'package:p_tits_pas/services/user_service.dart';
|
||||||
|
|
||||||
@ -41,9 +42,15 @@ class _AdminUserFormDialogState extends State<AdminUserFormDialog> {
|
|||||||
bool _isLoadingRelais = true;
|
bool _isLoadingRelais = true;
|
||||||
List<RelaisModel> _relais = [];
|
List<RelaisModel> _relais = [];
|
||||||
String? _selectedRelaisId;
|
String? _selectedRelaisId;
|
||||||
|
String? _currentUserId;
|
||||||
bool get _isEditMode => widget.initialUser != null;
|
bool get _isEditMode => widget.initialUser != null;
|
||||||
bool get _isSuperAdminTarget =>
|
bool get _isSuperAdminTarget =>
|
||||||
(widget.initialUser?.role ?? '').toLowerCase() == 'super_admin';
|
(widget.initialUser?.role ?? '').toLowerCase() == 'super_admin';
|
||||||
|
bool get _isSelfTarget =>
|
||||||
|
_isEditMode &&
|
||||||
|
_currentUserId != null &&
|
||||||
|
widget.initialUser!.id == _currentUserId;
|
||||||
|
bool get _canDeleteTarget => !_isSuperAdminTarget && !_isSelfTarget;
|
||||||
bool get _isLockedAdminIdentity =>
|
bool get _isLockedAdminIdentity =>
|
||||||
_isEditMode && widget.adminMode && _isSuperAdminTarget;
|
_isEditMode && widget.adminMode && _isSuperAdminTarget;
|
||||||
String get _targetRoleKey {
|
String get _targetRoleKey {
|
||||||
@ -109,6 +116,23 @@ class _AdminUserFormDialogState extends State<AdminUserFormDialog> {
|
|||||||
} else {
|
} else {
|
||||||
_isLoadingRelais = false;
|
_isLoadingRelais = false;
|
||||||
}
|
}
|
||||||
|
_loadCurrentUserId();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadCurrentUserId() async {
|
||||||
|
final cached = await AuthService.getCurrentUser();
|
||||||
|
if (!mounted) return;
|
||||||
|
if (cached != null) {
|
||||||
|
setState(() {
|
||||||
|
_currentUserId = cached.id;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final refreshed = await AuthService.refreshCurrentUser();
|
||||||
|
if (!mounted || refreshed == null) return;
|
||||||
|
setState(() {
|
||||||
|
_currentUserId = refreshed.id;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -335,7 +359,7 @@ class _AdminUserFormDialogState extends State<AdminUserFormDialog> {
|
|||||||
|
|
||||||
Future<void> _delete() async {
|
Future<void> _delete() async {
|
||||||
if (widget.readOnly) return;
|
if (widget.readOnly) return;
|
||||||
if (_isSuperAdminTarget) return;
|
if (!_canDeleteTarget) return;
|
||||||
if (!_isEditMode || _isSubmitting) return;
|
if (!_isEditMode || _isSubmitting) return;
|
||||||
|
|
||||||
final confirmed = await showDialog<bool>(
|
final confirmed = await showDialog<bool>(
|
||||||
@ -462,7 +486,7 @@ class _AdminUserFormDialogState extends State<AdminUserFormDialog> {
|
|||||||
child: const Text('Fermer'),
|
child: const Text('Fermer'),
|
||||||
),
|
),
|
||||||
] else if (_isEditMode) ...[
|
] else if (_isEditMode) ...[
|
||||||
if (!_isSuperAdminTarget)
|
if (_canDeleteTarget)
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onPressed: _isSubmitting ? null : _delete,
|
onPressed: _isSubmitting ? null : _delete,
|
||||||
style: OutlinedButton.styleFrom(foregroundColor: Colors.red.shade700),
|
style: OutlinedButton.styleFrom(foregroundColor: Colors.red.shade700),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user