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