feat(frontend): simulate authenticated user sessions
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import "package:flutter/material.dart";
|
||||
import "package:google_fonts/google_fonts.dart";
|
||||
import "../demo_identity.dart";
|
||||
import "../services/auth_service.dart";
|
||||
import "../theme/ensup_colors.dart";
|
||||
import "../widgets/ensup_top_bar.dart";
|
||||
import "../widgets/profile_card.dart";
|
||||
@@ -13,6 +14,11 @@ import "restitution_select_screen.dart";
|
||||
class HomeScreen extends StatelessWidget {
|
||||
const HomeScreen({super.key});
|
||||
|
||||
void _quitter(BuildContext context) {
|
||||
AuthService.clearSession();
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -42,7 +48,7 @@ class HomeScreen extends StatelessWidget {
|
||||
campusTag: DemoIdentity.campusTag,
|
||||
userName: DemoIdentity.userName,
|
||||
userInitials: DemoIdentity.userInitials,
|
||||
onBack: () => Navigator.of(context).pop(),
|
||||
onBack: () => _quitter(context),
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import "package:flutter/material.dart";
|
||||
import "package:google_fonts/google_fonts.dart";
|
||||
import "../demo_identity.dart";
|
||||
import "../services/auth_service.dart";
|
||||
import "../theme/ensup_colors.dart";
|
||||
import "../widgets/ensup_top_bar.dart";
|
||||
import "../widgets/identification_option.dart";
|
||||
@@ -13,6 +14,7 @@ class LoginScreen extends StatelessWidget {
|
||||
const LoginScreen({super.key});
|
||||
|
||||
void _identifier(BuildContext context) {
|
||||
AuthService.selectDemoStudent();
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => const HomeScreen(),
|
||||
@@ -21,6 +23,16 @@ class LoginScreen extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void _identifierResponsable(BuildContext context) {
|
||||
AuthService.selectDemoResponsable();
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => const ResponsableScreen(),
|
||||
settings: const RouteSettings(name: "responsable"),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -162,15 +174,7 @@ class LoginScreen extends StatelessWidget {
|
||||
width: double.infinity,
|
||||
child: OutlinedButton.icon(
|
||||
onPressed: () =>
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) =>
|
||||
const ResponsableScreen(),
|
||||
settings: const RouteSettings(
|
||||
name: "responsable",
|
||||
),
|
||||
),
|
||||
),
|
||||
_identifierResponsable(context),
|
||||
icon: const Icon(
|
||||
Icons.person_outline,
|
||||
size: 16,
|
||||
|
||||
@@ -2,6 +2,7 @@ import "package:flutter/material.dart";
|
||||
import "package:google_fonts/google_fonts.dart";
|
||||
|
||||
import "../demo_identity.dart";
|
||||
import "../services/auth_service.dart";
|
||||
import "../services/responsable_service.dart";
|
||||
import "../theme/ensup_colors.dart";
|
||||
import "../utils/file_download.dart";
|
||||
@@ -44,6 +45,11 @@ class _ResponsableScreenState extends State<ResponsableScreen> {
|
||||
setState(() => _future = _charger());
|
||||
}
|
||||
|
||||
void _quitter() {
|
||||
AuthService.clearSession();
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
Future<void> _changerStatut(Map<String, dynamic> anomalie) async {
|
||||
final prochain = _prochainStatut(anomalie["statut"] as String?);
|
||||
if (prochain == null || _transitionEnCours) {
|
||||
@@ -124,10 +130,7 @@ class _ResponsableScreenState extends State<ResponsableScreen> {
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
_ResponsableTopBar(
|
||||
onExit: () => Navigator.of(context).pop(),
|
||||
onRefresh: _rafraichir,
|
||||
),
|
||||
_ResponsableTopBar(onExit: _quitter, onRefresh: _rafraichir),
|
||||
Expanded(
|
||||
child: FutureBuilder<_ResponsableData>(
|
||||
future: _future,
|
||||
@@ -249,7 +252,7 @@ class _ResponsableScreenState extends State<ResponsableScreen> {
|
||||
anomaliesActives: anomaliesActives,
|
||||
notificationsNonLues: notificationsNonLues,
|
||||
onChanged: (index) => setState(() => _onglet = index),
|
||||
onExit: () => Navigator.of(context).pop(),
|
||||
onExit: _quitter,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import "dart:convert";
|
||||
import "dart:typed_data";
|
||||
import "package:http/http.dart" as http;
|
||||
import "auth_service.dart";
|
||||
|
||||
/// Erreur levée par le client API (serveur injoignable ou réponse en échec).
|
||||
class ApiException implements Exception {
|
||||
@@ -24,27 +25,19 @@ class ApiFile {
|
||||
final String contentType;
|
||||
}
|
||||
|
||||
/// Point d'accès unique au backend : centralise l'URL de base, l'en-tête
|
||||
/// d'authentification (simulée) et la gestion des erreurs.
|
||||
/// Point d'accès unique au backend : centralise l'URL, l'authentification et
|
||||
/// la gestion des erreurs.
|
||||
class ApiClient {
|
||||
ApiClient._();
|
||||
|
||||
static const String _baseUrl = "http://localhost:3000/api";
|
||||
static const String _utilisateurEmail = "lucas.martin@ensitech.eu";
|
||||
static const String responsableEmail = "karim.benali@ensup.eu";
|
||||
|
||||
static Future<dynamic> get(
|
||||
String chemin, {
|
||||
String utilisateurEmail = _utilisateurEmail,
|
||||
}) async {
|
||||
static Future<dynamic> get(String chemin) async {
|
||||
final uri = Uri.parse("$_baseUrl$chemin");
|
||||
|
||||
late final http.Response reponse;
|
||||
try {
|
||||
reponse = await http.get(
|
||||
uri,
|
||||
headers: {"x-user-email": utilisateurEmail},
|
||||
);
|
||||
reponse = await http.get(uri, headers: AuthService.requestHeaders());
|
||||
} catch (_) {
|
||||
throw const ApiException(
|
||||
"Impossible de joindre le serveur. Vérifiez que le backend est démarré.",
|
||||
@@ -60,16 +53,12 @@ class ApiClient {
|
||||
static Future<ApiFile> getFile(
|
||||
String chemin, {
|
||||
required String fallbackFileName,
|
||||
String utilisateurEmail = _utilisateurEmail,
|
||||
}) async {
|
||||
final uri = Uri.parse("$_baseUrl$chemin");
|
||||
|
||||
late final http.Response reponse;
|
||||
try {
|
||||
reponse = await http.get(
|
||||
uri,
|
||||
headers: {"x-user-email": utilisateurEmail},
|
||||
);
|
||||
reponse = await http.get(uri, headers: AuthService.requestHeaders());
|
||||
} catch (_) {
|
||||
throw const ApiException(
|
||||
"Impossible de joindre le serveur. Vérifiez que le backend est démarré.",
|
||||
@@ -88,11 +77,7 @@ class ApiClient {
|
||||
);
|
||||
}
|
||||
|
||||
static Future<dynamic> post(
|
||||
String chemin,
|
||||
Map<String, dynamic> body, {
|
||||
String utilisateurEmail = _utilisateurEmail,
|
||||
}) async {
|
||||
static Future<dynamic> post(String chemin, Map<String, dynamic> body) async {
|
||||
final uri = Uri.parse("$_baseUrl$chemin");
|
||||
|
||||
late final http.Response reponse;
|
||||
@@ -100,8 +85,8 @@ class ApiClient {
|
||||
reponse = await http.post(
|
||||
uri,
|
||||
headers: {
|
||||
...AuthService.requestHeaders(),
|
||||
"content-type": "application/json",
|
||||
"x-user-email": utilisateurEmail,
|
||||
},
|
||||
body: jsonEncode(body),
|
||||
);
|
||||
@@ -117,11 +102,7 @@ class ApiClient {
|
||||
throw ApiException(_messageErreur(reponse));
|
||||
}
|
||||
|
||||
static Future<dynamic> patch(
|
||||
String chemin,
|
||||
Map<String, dynamic> body, {
|
||||
String utilisateurEmail = _utilisateurEmail,
|
||||
}) async {
|
||||
static Future<dynamic> patch(String chemin, Map<String, dynamic> body) async {
|
||||
final uri = Uri.parse("$_baseUrl$chemin");
|
||||
|
||||
late final http.Response reponse;
|
||||
@@ -129,8 +110,8 @@ class ApiClient {
|
||||
reponse = await http.patch(
|
||||
uri,
|
||||
headers: {
|
||||
...AuthService.requestHeaders(),
|
||||
"content-type": "application/json",
|
||||
"x-user-email": utilisateurEmail,
|
||||
},
|
||||
body: jsonEncode(body),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
enum AuthenticationMode { demo, azure }
|
||||
|
||||
class AuthService {
|
||||
AuthService._();
|
||||
|
||||
static const String _configuredMode = String.fromEnvironment(
|
||||
"AUTH_MODE",
|
||||
defaultValue: "demo",
|
||||
);
|
||||
static const String azureTenantId = String.fromEnvironment("AZURE_TENANT_ID");
|
||||
static const String azureSpaClientId = String.fromEnvironment(
|
||||
"AZURE_SPA_CLIENT_ID",
|
||||
);
|
||||
static const String azureApiScope = String.fromEnvironment("AZURE_API_SCOPE");
|
||||
|
||||
static const String demoStudentEmail = "lucas.martin@ensitech.eu";
|
||||
static const String demoResponsableEmail = "karim.benali@ensup.eu";
|
||||
|
||||
static String? _demoEmail;
|
||||
|
||||
static AuthenticationMode get mode {
|
||||
return switch (_configuredMode) {
|
||||
"demo" => AuthenticationMode.demo,
|
||||
"azure" => AuthenticationMode.azure,
|
||||
_ => throw StateError("AUTH_MODE doit valoir demo ou azure."),
|
||||
};
|
||||
}
|
||||
|
||||
static void selectDemoStudent() {
|
||||
_ensureDemoMode();
|
||||
_demoEmail = demoStudentEmail;
|
||||
}
|
||||
|
||||
static void selectDemoResponsable() {
|
||||
_ensureDemoMode();
|
||||
_demoEmail = demoResponsableEmail;
|
||||
}
|
||||
|
||||
static void clearSession() {
|
||||
_demoEmail = null;
|
||||
}
|
||||
|
||||
static Map<String, String> requestHeaders() {
|
||||
if (mode == AuthenticationMode.azure) {
|
||||
throw StateError("La session Microsoft n'est pas encore initialisee.");
|
||||
}
|
||||
|
||||
final email = _demoEmail;
|
||||
if (email == null) {
|
||||
throw StateError("Aucun utilisateur de demonstration selectionne.");
|
||||
}
|
||||
|
||||
return {"x-user-email": email};
|
||||
}
|
||||
|
||||
static void _ensureDemoMode() {
|
||||
if (mode != AuthenticationMode.demo) {
|
||||
throw StateError(
|
||||
"Une identite de demonstration est interdite en mode Azure.",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,8 @@ import "api_client.dart";
|
||||
class ResponsableService {
|
||||
ResponsableService._();
|
||||
|
||||
static const String _email = ApiClient.responsableEmail;
|
||||
|
||||
static Future<Map<String, dynamic>> dashboard() async {
|
||||
final reponse = await ApiClient.get(
|
||||
"/responsable/dashboard",
|
||||
utilisateurEmail: _email,
|
||||
);
|
||||
final reponse = await ApiClient.get("/responsable/dashboard");
|
||||
return Map<String, dynamic>.from(reponse["data"] as Map);
|
||||
}
|
||||
|
||||
@@ -32,14 +27,12 @@ class ResponsableService {
|
||||
static Future<ApiFile> exporterHistorique() => ApiClient.getFile(
|
||||
"/responsable/historique/export.csv",
|
||||
fallbackFileName: "historique-responsable.csv",
|
||||
utilisateurEmail: _email,
|
||||
);
|
||||
|
||||
static Future<int> marquerNotificationsLues() async {
|
||||
final reponse = await ApiClient.patch(
|
||||
"/responsable/notifications/lu-toutes",
|
||||
{},
|
||||
utilisateurEmail: _email,
|
||||
);
|
||||
final data = Map<String, dynamic>.from(reponse["data"] as Map);
|
||||
return data["count"] as int;
|
||||
@@ -52,12 +45,12 @@ class ResponsableService {
|
||||
final reponse = await ApiClient.patch("/responsable/anomalies/$id/statut", {
|
||||
"statut": statut,
|
||||
"observation": "Traitement depuis l'interface responsable",
|
||||
}, utilisateurEmail: _email);
|
||||
});
|
||||
return Map<String, dynamic>.from(reponse["data"] as Map);
|
||||
}
|
||||
|
||||
static Future<List<Map<String, dynamic>>> _liste(String chemin) async {
|
||||
final reponse = await ApiClient.get(chemin, utilisateurEmail: _email);
|
||||
final reponse = await ApiClient.get(chemin);
|
||||
final data = reponse["data"] as List;
|
||||
return data.map((item) => Map<String, dynamic>.from(item as Map)).toList();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user