diff --git a/.firebaserc b/.firebaserc
new file mode 100644
index 00000000..7dd880b5
--- /dev/null
+++ b/.firebaserc
@@ -0,0 +1,5 @@
+{
+ "projects": {
+ "default": "spring2025-81f5b"
+ }
+}
diff --git a/.gitignore b/.gitignore
index 3a83c2f0..a0883058 100644
Binary files a/.gitignore and b/.gitignore differ
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 00000000..a91a9154
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,94 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Flutter",
+ "type": "dart",
+ "request": "launch",
+ "program": "lib/main.dart"
+ },
+ {
+ "name": "team_a",
+ "cwd": "team_a",
+ "request": "launch",
+ "type": "dart"
+ },
+ {
+ "name": "team_a (profile mode)",
+ "cwd": "team_a",
+ "request": "launch",
+ "type": "dart",
+ "flutterMode": "profile"
+ },
+ {
+ "name": "team_a (release mode)",
+ "cwd": "team_a",
+ "request": "launch",
+ "type": "dart",
+ "flutterMode": "release"
+ },
+ {
+ "name": "stml_application",
+ "cwd": "STML\\stml_application",
+ "request": "launch",
+ "type": "dart"
+ },
+ {
+ "name": "stml_application (profile mode)",
+ "cwd": "STML\\stml_application",
+ "request": "launch",
+ "type": "dart",
+ "flutterMode": "profile"
+ },
+ {
+ "name": "stml_application (release mode)",
+ "cwd": "STML\\stml_application",
+ "request": "launch",
+ "type": "dart",
+ "flutterMode": "release"
+ },
+ {
+ "name": "teamA",
+ "cwd": "team_a\\teamA",
+ "request": "launch",
+ "type": "dart"
+ },
+ {
+ "name": "teamA (profile mode)",
+ "cwd": "team_a\\teamA",
+ "request": "launch",
+ "type": "dart",
+ "flutterMode": "profile"
+ },
+ {
+ "name": "teamA (release mode)",
+ "cwd": "team_a\\teamA",
+ "request": "launch",
+ "type": "dart",
+ "flutterMode": "release"
+ },
+ {
+ "name": "yappy",
+ "cwd": "team_b\\yappy",
+ "request": "launch",
+ "type": "dart"
+ },
+ {
+ "name": "yappy (profile mode)",
+ "cwd": "team_b\\yappy",
+ "request": "launch",
+ "type": "dart",
+ "flutterMode": "profile"
+ },
+ {
+ "name": "yappy (release mode)",
+ "cwd": "team_b\\yappy",
+ "request": "launch",
+ "type": "dart",
+ "flutterMode": "release"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..88550be3
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,4 @@
+{
+ "cmake.ignoreCMakeListsMissing": true,
+ "dart.flutterSdkPath": "C:\\flutter_windows_3.29.0-stable\\flutter"
+}
\ No newline at end of file
diff --git a/STML/stml_application/android/app/build.gradle b/STML/stml_application/android/app/build.gradle
index a7b7327d..52e942fe 100644
--- a/STML/stml_application/android/app/build.gradle
+++ b/STML/stml_application/android/app/build.gradle
@@ -9,7 +9,7 @@ plugins {
android {
namespace = "com.umgc.memoryminder"
compileSdk = 35
- ndkVersion = "27.0.12077973"
+ ndkVersion = "29.0.13113456"
compileOptions {
coreLibraryDesugaringEnabled true
diff --git a/STML/stml_application/android/app/src/main/AndroidManifest.xml b/STML/stml_application/android/app/src/main/AndroidManifest.xml
index 0064a8e8..c13b29df 100644
--- a/STML/stml_application/android/app/src/main/AndroidManifest.xml
+++ b/STML/stml_application/android/app/src/main/AndroidManifest.xml
@@ -1,14 +1,26 @@
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
sendHelpNotification() async {
+ // Remplacez 'caregiver_device_token' par le token du soignant
+ await _firebaseMessaging.sendMessage(
+ to: 'caregiver_device_token',
+ data: {
+ 'type': 'help_request',
+ 'message': 'The STML user has requested help.',
+ },
+ );
+ }
+}
diff --git a/STML/stml_application/lib/services/notification_service.dart b/STML/stml_application/lib/services/notification_service.dart
new file mode 100644
index 00000000..b88ef896
--- /dev/null
+++ b/STML/stml_application/lib/services/notification_service.dart
@@ -0,0 +1,249 @@
+import 'package:firebase_auth/firebase_auth.dart';
+import 'package:firebase_messaging/firebase_messaging.dart';
+import 'package:flutter_local_notifications/flutter_local_notifications.dart';
+import 'package:cloud_functions/cloud_functions.dart';
+import 'package:geolocator/geolocator.dart';
+import 'package:geocoding/geocoding.dart';
+import 'package:memoryminder/ui/location_history_screen.dart';
+import 'package:firebase_analytics/firebase_analytics.dart';
+
+class NotificationService {
+ final FirebaseMessaging _firebaseMessaging = FirebaseMessaging.instance;
+ final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
+ FlutterLocalNotificationsPlugin();
+ final FirebaseAnalytics _analytics = FirebaseAnalytics.instance;
+
+ String? _caregiverToken;
+ String? _lastRequestId;
+
+ String? get lastRequestId => _lastRequestId;
+
+ Future initialize() async {
+ // Get the FCM token for the current device
+ _caregiverToken = await _firebaseMessaging.getToken();
+ print("Caregiver Token: $_caregiverToken");
+
+ // Log token generation for analytics
+ await _analytics.logEvent(
+ name: 'fcm_token_generated',
+ parameters: {
+ 'user_type': 'caregiver',
+ },
+ );
+
+ // Listen for new tokens (in case the token changes)
+ _firebaseMessaging.onTokenRefresh.listen((newToken) {
+ _caregiverToken = newToken;
+ print("New Caregiver Token: $_caregiverToken");
+
+ // Log token refresh for analytics
+ _analytics.logEvent(
+ name: 'fcm_token_refreshed',
+ parameters: {
+ 'user_type': 'caregiver',
+ },
+ );
+ });
+
+ // Configure local notifications
+ const AndroidInitializationSettings initializationSettingsAndroid =
+ AndroidInitializationSettings('@mipmap/ic_launcher');
+ final InitializationSettings initializationSettings =
+ InitializationSettings(
+ android: initializationSettingsAndroid,
+ );
+ await _flutterLocalNotificationsPlugin.initialize(initializationSettings);
+
+ // Listen for incoming messages
+ FirebaseMessaging.onMessage.listen((RemoteMessage message) {
+ print("Message received: ${message.notification?.title}");
+ _showNotification(message);
+
+ // Log message received for analytics
+ _analytics.logEvent(
+ name: 'notification_received',
+ parameters: {
+ 'notification_type': message.data['type'] ?? 'unknown',
+ 'has_notification': message.notification != null,
+ },
+ );
+ });
+ }
+
+ Future _showNotification(RemoteMessage message) async {
+ const AndroidNotificationDetails androidPlatformChannelSpecifics =
+ AndroidNotificationDetails(
+ 'your_channel_id', // Notification channel ID
+ 'your_channel_name', // Notification channel name
+ importance: Importance.max,
+ priority: Priority.high,
+ );
+ const NotificationDetails platformChannelSpecifics =
+ NotificationDetails(android: androidPlatformChannelSpecifics);
+
+ await _flutterLocalNotificationsPlugin.show(
+ 0, // Notification ID
+ message.notification?.title, // Notification title
+ message.notification?.body, // Notification body
+ platformChannelSpecifics,
+ );
+
+ // Log notification shown for analytics
+ await _analytics.logEvent(
+ name: 'notification_displayed',
+ parameters: {
+ 'notification_title': message.notification?.title ?? 'No title',
+ },
+ );
+ }
+
+ Future sendHelpNotification(LocationEntry? currentLocationEntry) async {
+ try {
+ // Start timer for performance tracking
+ final startTime = DateTime.now();
+
+ // Prepare location data
+ Map? locationData;
+
+ if (currentLocationEntry != null) {
+ try {
+ // Try to get the current coordinates
+ Position currentPosition = await Geolocator.getCurrentPosition();
+
+ // Create the data map with the stored address and current coordinates
+ locationData = {
+ 'address': currentLocationEntry.address,
+ 'latitude': currentPosition.latitude,
+ 'longitude': currentPosition.longitude,
+ 'timestamp': currentLocationEntry.startTime.toIso8601String()
+ };
+
+ // Log successful location capture
+ await _analytics.logEvent(
+ name: 'emergency_location_captured',
+ parameters: {
+ 'method': 'geolocator',
+ 'has_coordinates': true,
+ },
+ );
+ } catch (e) {
+ // Log location error
+ await _analytics.logEvent(
+ name: 'emergency_location_error',
+ parameters: {
+ 'error_type': 'geolocator_error',
+ 'error_message': e.toString(),
+ },
+ );
+
+ // Fallback: try to geocode the address to get coordinates
+ try {
+ List locations =
+ await locationFromAddress(currentLocationEntry.address);
+ if (locations.isNotEmpty) {
+ locationData = {
+ 'address': currentLocationEntry.address,
+ 'latitude': locations.first.latitude,
+ 'longitude': locations.first.longitude,
+ 'timestamp': currentLocationEntry.startTime.toIso8601String()
+ };
+
+ // Log successful geocoding
+ await _analytics.logEvent(
+ name: 'emergency_location_captured',
+ parameters: {
+ 'method': 'geocoding',
+ 'has_coordinates': true,
+ },
+ );
+ } else {
+ // If geocoding fails, only send the address
+ locationData = {
+ 'address': currentLocationEntry.address,
+ 'timestamp': currentLocationEntry.startTime.toIso8601String()
+ };
+
+ // Log geocoding with no results
+ await _analytics.logEvent(
+ name: 'emergency_location_captured',
+ parameters: {
+ 'method': 'geocoding',
+ 'has_coordinates': false,
+ },
+ );
+ }
+ } catch (e) {
+ // Log geocoding error
+ await _analytics.logEvent(
+ name: 'emergency_location_error',
+ parameters: {
+ 'error_type': 'geocoding_error',
+ 'error_message': e.toString(),
+ },
+ );
+
+ // As a last resort, only send the address
+ locationData = {
+ 'address': currentLocationEntry.address,
+ 'timestamp': currentLocationEntry.startTime.toIso8601String()
+ };
+ }
+ }
+ } else {
+ // Log no location available
+ await _analytics.logEvent(
+ name: 'emergency_location_missing',
+ );
+ }
+
+ // Call the Cloud Function
+ final HttpsCallable callable =
+ FirebaseFunctions.instance.httpsCallable('sendHelpAlert');
+ final result = await callable.call({
+ 'caregiverToken': _caregiverToken,
+ 'location': locationData,
+ 'userId': FirebaseAuth.instance.currentUser?.uid,
+ 'userName': FirebaseAuth.instance.currentUser?.displayName
+ });
+
+ // Save the request ID for later reference
+ _lastRequestId = result.data['requestId'];
+
+ // Calculate time taken
+ final endTime = DateTime.now();
+ final duration = endTime.difference(startTime).inMilliseconds;
+
+ // Create parameters map without nullable values
+ final Map analyticsParams = {
+ 'success': result.data['success'] ?? false,
+ 'has_location': currentLocationEntry != null,
+ 'processing_time_ms': duration,
+ };
+
+ // Add requestId only if it's not null
+ if (_lastRequestId != null) {
+ analyticsParams['request_id'] = _lastRequestId!;
+ }
+
+ // Log complete event with success/failure
+ await _analytics.logEvent(
+ name: 'emergency_alert_sent',
+ parameters: analyticsParams,
+ );
+
+ return result.data['success'] ?? false;
+ } catch (e) {
+ print("Error sending notification: $e");
+
+ // Log error event
+ await _analytics.logEvent(
+ name: 'emergency_alert_error',
+ parameters: {
+ 'error_message': e.toString(),
+ },
+ );
+
+ return false;
+ }
+ }
+}
diff --git a/STML/stml_application/lib/src/features/caregiver-dashboard/presentation/caregiver-dashboard.dart b/STML/stml_application/lib/src/features/caregiver-dashboard/presentation/caregiver-dashboard.dart
index 26949d01..5a6dfd6e 100644
--- a/STML/stml_application/lib/src/features/caregiver-dashboard/presentation/caregiver-dashboard.dart
+++ b/STML/stml_application/lib/src/features/caregiver-dashboard/presentation/caregiver-dashboard.dart
@@ -10,10 +10,10 @@ import 'package:memoryminder/src/features/caregiver-dashboard/presentation/care_
import 'package:memoryminder/src/features/caregiver-dashboard/service/manage_care_recipient_service.dart';
import 'package:memoryminder/src/features/caregiver-dashboard/service/notification_service.dart';
import 'package:memoryminder/src/features/caregiver-dashboard/service/notification_stream_service.dart';
-import 'package:memoryminder/ui/profile_screen.dart';
import 'package:memoryminder/src/utils/ui_utils.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
+import 'package:url_launcher/url_launcher.dart';
// Main HomeScreen widget which is a stateless widget.
class CaregiverDashboardScreen extends StatefulWidget {
@@ -33,6 +33,15 @@ class _CaregiverDashboardScreen extends State {
_careRecipientData = ManageCareRecipientService().getAllCareRecipients();
}
+ Future _callEmergencyNumber() async {
+ final Uri phoneUri = Uri.parse('tel:911');
+ if (await canLaunchUrl(phoneUri)) {
+ await launchUrl(phoneUri);
+ } else {
+ throw 'Could not launch $phoneUri';
+ }
+ }
+
@override
void dispose() {
NotificationStreamService().dispose();
@@ -47,7 +56,6 @@ class _CaregiverDashboardScreen extends State {
appBar: const CustomAppBar(
title: 'Caregiver Dashboard',
),
-
body: Container(
/*decoration: BoxDecoration(
image: DecorationImage(
@@ -112,7 +120,6 @@ class _CaregiverDashboardScreen extends State {
),
),
),
-
const Divider(
color: Colors.black54,
thickness: 2,
@@ -120,7 +127,33 @@ class _CaregiverDashboardScreen extends State {
indent: 20,
endIndent: 20,
),
-
+ const Divider(
+ color: Colors.black54,
+ thickness: 2,
+ height: 10,
+ indent: 20,
+ endIndent: 20,
+ ),
+ ElevatedButton(
+ onPressed: _callEmergencyNumber,
+ style: ElevatedButton.styleFrom(
+ backgroundColor:
+ Colors.red, // Couleur rouge pour indiquer l'urgence
+ padding:
+ const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
+ ),
+ child: const Text(
+ 'Emergency Call',
+ style: TextStyle(fontSize: 18, color: Colors.white),
+ ),
+ ),
+ const Divider(
+ color: Colors.black54,
+ thickness: 2,
+ height: 10,
+ indent: 20,
+ endIndent: 20,
+ ),
Padding(
padding: EdgeInsets.fromLTRB(2.0, 2, 2.0, 2),
child: Column(
@@ -138,7 +171,8 @@ class _CaregiverDashboardScreen extends State {
onPressed: () {
Navigator.push(
context,
- MaterialPageRoute(builder: (context) => AddCareRecipientForm()),
+ MaterialPageRoute(
+ builder: (context) => AddCareRecipientForm()),
);
},
icon: Icon(
@@ -149,18 +183,17 @@ class _CaregiverDashboardScreen extends State {
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.black,
- padding: EdgeInsets.fromLTRB(2.0, 2, 16.0, 2), // Apply padding here
+ padding: EdgeInsets.fromLTRB(
+ 2.0, 2, 16.0, 2), // Apply padding here
),
),
],
),
),
-
-
Expanded(
- child: _buildCareRecipientsGrid(
- context: context,
- ),
+ child: _buildCareRecipientsGrid(
+ context: context,
+ ),
),
const Divider(
color: Colors.black,
@@ -187,61 +220,64 @@ class _CaregiverDashboardScreen extends State {
final data = snapshot.data!;
return GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
- crossAxisCount: 3, // Adjust as needed
- childAspectRatio: 1.30,
- mainAxisSpacing: 10,
- crossAxisSpacing: 10
- ),
+ crossAxisCount: 3, // Adjust as needed
+ childAspectRatio: 1.0,
+ mainAxisSpacing: 3,
+ crossAxisSpacing: 3),
itemCount: data.length,
itemBuilder: (context, index) {
final item = data[index];
- final String labelText = '${item['firstName'].toString()} ${item['lastName'].toString()}';
+ final String labelText =
+ '${item['firstName'].toString()} ${item['lastName'].toString()}';
final careRecipient = CareRecipient.fromMap(item);
- return InkWell( // Or InkWell for ripple effect
- onTap: () {
- // Handle item click
- print('Item ${item['firstName'].toString()} clicked');
- Navigator.push(
- context,
- MaterialPageRoute(builder: (context) => CareRecipientProfileScreen(careRecipientId: item['itemId'].toString(), careRecipientData: careRecipient.toMap())),
- );
- },
- borderRadius: BorderRadius.circular(12.0),
- child: Container(
- decoration: BoxDecoration(
- color: Colors.lightBlue[100],
- borderRadius: BorderRadius.circular(12.0),
- boxShadow: [
- BoxShadow(
- color: Colors.lightBlueAccent,
- spreadRadius: 1,
- blurRadius: 3,
- offset: const Offset(0, 2),
- ),
- ],
- ),
- padding: const EdgeInsets.all(16.0),
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Icon(
- Icons.person,
- size: 40.0,
- color: Color.fromARGB(255, 2, 63, 129),
- ),
- const SizedBox(height: 8.0),
- Text(
- labelText,
- textAlign: TextAlign.center,
- style: const TextStyle(
- fontWeight: FontWeight.w500,
- color: Colors.black87,
+ return InkWell(
+ // Or InkWell for ripple effect
+ onTap: () {
+ // Handle item click
+ print('Item ${item['firstName'].toString()} clicked');
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) => CareRecipientProfileScreen(
+ careRecipientId: item['itemId'].toString(),
+ careRecipientData: careRecipient.toMap())),
+ );
+ },
+ borderRadius: BorderRadius.circular(12.0),
+ child: Container(
+ decoration: BoxDecoration(
+ color: Colors.lightGreen[100],
+ borderRadius: BorderRadius.circular(12.0),
+ boxShadow: [
+ BoxShadow(
+ color: Colors.lightGreen.withOpacity(0.5),
+ spreadRadius: 1,
+ blurRadius: 3,
+ offset: const Offset(0, 2),
+ ),
+ ],
+ ),
+ padding: const EdgeInsets.all(3.0),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Icon(
+ Icons.person,
+ size: 40.0,
+ color: Colors.green,
+ ),
+ const SizedBox(height: 8.0),
+ Text(
+ labelText,
+ textAlign: TextAlign.center,
+ style: const TextStyle(
+ fontWeight: FontWeight.w500,
+ color: Colors.black87,
),
),
],
),
- )
- );
+ ));
},
);
} else if (snapshot.hasError) {
@@ -300,7 +336,6 @@ class _CaregiverDashboardScreen extends State {
notificationService
.markNotificationAsRead(notification['id']);
},
-
);
},
));
@@ -308,5 +343,3 @@ class _CaregiverDashboardScreen extends State {
);
}
}
-
-
diff --git a/STML/stml_application/lib/src/features/caregiver-dashboard/service/notification_service.dart b/STML/stml_application/lib/src/features/caregiver-dashboard/service/notification_service.dart
index 2742da48..a2a93695 100644
--- a/STML/stml_application/lib/src/features/caregiver-dashboard/service/notification_service.dart
+++ b/STML/stml_application/lib/src/features/caregiver-dashboard/service/notification_service.dart
@@ -7,29 +7,34 @@ import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:memoryminder/src/features/caregiver-dashboard/service/notification_stream_service.dart';
-
-
+import 'package:url_launcher/url_launcher.dart';
class NotificationService {
final FirebaseMessaging _messaging = FirebaseMessaging.instance;
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
- final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
+ final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
+ FlutterLocalNotificationsPlugin();
Future initialize() async {
await _messaging.subscribeToTopic("STML_USER_PRESSED_HELP");
- _messaging.requestPermission(alert: true,
+ _messaging.requestPermission(
+ alert: true,
announcement: true,
badge: true,
carPlay: false,
criticalAlert: true,
provisional: false,
- sound: true,);
+ sound: true,
+ );
FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
- print('---------------------------------------------------------------------');
- print('---Received a notification message: ${message.notification?.title}---');
- print('---------------------------------------------------------------------');
+ print(
+ '---------------------------------------------------------------------');
+ print(
+ '---Received a notification message: ${message.notification?.title}---');
+ print(
+ '---------------------------------------------------------------------');
_storeMessage(message);
await Future.delayed(Duration(seconds: 5));
@@ -38,25 +43,43 @@ class NotificationService {
});
FirebaseMessaging.onBackgroundMessage(_backgroundMessageHandler);
- const AndroidInitializationSettings androidInitializationSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
- const DarwinInitializationSettings iOSinitializationSettings = DarwinInitializationSettings(
+
+ const AndroidInitializationSettings androidInitializationSettings =
+ AndroidInitializationSettings('@mipmap/ic_launcher');
+ const DarwinInitializationSettings iOSinitializationSettings =
+ DarwinInitializationSettings(
requestAlertPermission: true,
requestBadgePermission: true,
requestSoundPermission: true,
);
- const InitializationSettings initializationSettings = InitializationSettings(android: androidInitializationSettings, iOS: iOSinitializationSettings);
- await flutterLocalNotificationsPlugin.initialize(initializationSettings);
-
+ const InitializationSettings initializationSettings =
+ InitializationSettings(
+ android: androidInitializationSettings,
+ iOS: iOSinitializationSettings,
+ );
+ await flutterLocalNotificationsPlugin.initialize(
+ initializationSettings,
+ onDidReceiveNotificationResponse: (NotificationResponse response) async {
+ if (response.actionId == 'emergency_call') {
+ const phoneNumber = 'tel:911';
+ if (await canLaunch(phoneNumber)) {
+ await launch(phoneNumber);
+ } else {
+ throw 'Could not launch $phoneNumber';
+ }
+ }
+ },
+ );
// Handle initial message when the app is opened from a terminated state
- RemoteMessage? initialMessage = await FirebaseMessaging.instance.getInitialMessage();
+ RemoteMessage? initialMessage =
+ await FirebaseMessaging.instance.getInitialMessage();
if (initialMessage != null) {
_handleMessage(initialMessage);
}
// Handle when the app is opened from background state.
FirebaseMessaging.onMessageOpenedApp.listen(_handleMessage);
-
}
static void _handleMessage(RemoteMessage message) {
@@ -65,72 +88,94 @@ class NotificationService {
// Example: Navigator.of(navigatorKey.currentContext!).pushNamed('/details', arguments: message.data);
}
-
-
- Future _showLocalNotification(RemoteMessage message) async{
- const AndroidNotificationDetails androidChannelSpecifics = AndroidNotificationDetails('Help', 'Help', channelDescription: 'User pressed Help button', importance: Importance.max, priority: Priority.high, ticker: 'ticker');
- const NotificationDetails channelSpecifics = NotificationDetails(android: androidChannelSpecifics, iOS: const DarwinNotificationDetails());
- await flutterLocalNotificationsPlugin.show(0, message.notification?.title, message.notification?.title, channelSpecifics);
-
+ Future _showLocalNotification(RemoteMessage message) async {
+ const AndroidNotificationDetails androidChannelSpecifics =
+ AndroidNotificationDetails(
+ 'Help', //Canal ID
+ 'Help Notifications', // canal name
+ channelDescription:
+ 'Notifications when the STML user presses the HELP button',
+ importance: Importance.max,
+ priority: Priority.high,
+ ticker: 'ticker',
+ enableVibration: true, //vibrations
+ playSound: true, // play tone
+ actions: [
+ AndroidNotificationAction(
+ 'emergency_call', // action Id
+ 'Emergency Call', // action text
+ ),
+ ],
+ );
+ const NotificationDetails channelSpecifics = NotificationDetails(
+ android: androidChannelSpecifics,
+ iOS: const DarwinNotificationDetails());
+ await flutterLocalNotificationsPlugin.show(0, message.notification?.title,
+ message.notification?.body, channelSpecifics);
}
Future _storeMessage(RemoteMessage message) async {
await _firestore.collection('notifications').add({
'title': message.notification?.title,
- 'read': false, //Flag to indicate if the message is read or not.
+ 'read': false, // Flag to indicate if the message is read or not.
'createdDate': FieldValue.serverTimestamp(),
+ 'stmlUserId': 'USER_ID', // Replaced by STML's userID
});
}
Future markNotificationAsRead(String messageId) async {
try {
- DocumentReference notificationRef = _firestore.collection('notifications').doc(messageId);
- await notificationRef.update({'read':true});
+ DocumentReference notificationRef =
+ _firestore.collection('notifications').doc(messageId);
+ await notificationRef.update({'read': true});
print('Notification updated as read');
getRecentNotifications();
-
- } catch(e) {
+ } catch (e) {
print('Error updating notification data: $e');
}
}
Future getRecentNotifications() async {
try {
- QuerySnapshot snapshot = await _firestore.collection('notifications')
+ QuerySnapshot snapshot = await _firestore
+ .collection('notifications')
.orderBy('createdDate', descending: true)
.limit(10)
.get();
List> notifications = snapshot.docs.map((doc) {
- return {'id': doc.id,
+ return {
+ 'id': doc.id,
'title': doc['title'],
'read': doc['read'],
'createDate': doc['createdDate'],
};
}).toList();
NotificationStreamService().addData(notifications);
- } catch(e) {
+ } catch (e) {
print('Error fetching notifications $e');
}
}
-Future sendNotificationToFirestore(String title) async {
- await FirebaseFirestore.instance.collection('notifications').add({
- 'title': title,
- 'read': false, // Mark as unread by default
- 'createdDate': FieldValue.serverTimestamp(),
- });
-
-}
+ Future sendNotificationToFirestore(String title) async {
+ await FirebaseFirestore.instance.collection('notifications').add({
+ 'title': title,
+ 'read': false, // Mark as unread by default
+ 'createdDate': FieldValue.serverTimestamp(),
+ });
+ }
}
Future _backgroundMessageHandler(RemoteMessage message) async {
- print('---------------------------------------------------------------------');
- print('---Received a background data message----${message.notification?.title}---------------------------');
- print('---------------------------------------------------------------------');
+ print(
+ '---------------------------------------------------------------------');
+ print(
+ '---Received a background data message----${message.notification?.title}---------------------------');
+ print(
+ '---------------------------------------------------------------------');
await Firebase.initializeApp();
NotificationService()._storeMessage(message);
await Future.delayed(Duration(seconds: 5));
NotificationService().getRecentNotifications();
NotificationService()._showLocalNotification(message);
-}
\ No newline at end of file
+}
diff --git a/STML/stml_application/lib/src/features/stml_user_dashboard/presentation/stml_user_dashboard.dart b/STML/stml_application/lib/src/features/stml_user_dashboard/presentation/stml_user_dashboard.dart
index ff208826..163c9bf4 100644
--- a/STML/stml_application/lib/src/features/stml_user_dashboard/presentation/stml_user_dashboard.dart
+++ b/STML/stml_application/lib/src/features/stml_user_dashboard/presentation/stml_user_dashboard.dart
@@ -1,13 +1,18 @@
// ignore_for_file: avoid_print, prefer_const_constructors
// Imported libraries and packages
+import 'package:memoryminder/services/notification_service.dart';
import 'package:memoryminder/src/features/caregiver-dashboard/presentation/app_bar.dart';
-import 'package:memoryminder/src/features/help/help_screen.dart';
+import 'package:memoryminder/src/features/caregiver-dashboard/presentation/caregiver-dashboard.dart';
+import 'package:memoryminder/ui/dementia_resources.dart';
+import 'package:memoryminder/ui/help_screen.dart';
import 'package:memoryminder/ui/response_screen.dart';
+import 'package:memoryminder/ui/assistant_screen.dart';
import 'package:memoryminder/src/features/sensitive_information_detection/presentation/audio_screen.dart';
import 'package:memoryminder/ui/gallery_screen.dart';
import 'package:memoryminder/ui/profile_screen.dart';
import 'package:memoryminder/ui/scam_detection_screen.dart';
+import 'package:memoryminder/ui/tour_screen.dart';
import 'package:memoryminder/ui/location_history_screen.dart';
import 'package:geocoding/geocoding.dart';
import 'package:geolocator/geolocator.dart';
@@ -15,15 +20,16 @@ import 'package:memoryminder/src/camera_manager.dart';
import 'package:memoryminder/src/utils/ui_utils.dart';
import 'package:flutter/material.dart';
import 'package:memoryminder/features/caregiver_task_management/caregiver_task_screen.dart';
+import 'package:memoryminder/src/features/wearable-integration/fitbit_login.dart';
// Main HomeScreen widget which is a stateless widget.
-class STMLUserDashboardScreen extends StatefulWidget {
+class HomeScreen extends StatefulWidget {
@override
- _STMLUserDashboardScreenState createState() => _STMLUserDashboardScreenState();
+ _HomeScreenState createState() => _HomeScreenState();
}
-class _STMLUserDashboardScreenState extends State {
+class _HomeScreenState extends State {
bool hasBeenInitialized = false;
double iconSize = 65;
@@ -127,13 +133,12 @@ class _STMLUserDashboardScreenState extends State {
const Color(0xFF000000).withOpacity(0.30)),
_buildElevatedButton(
context: context,
- icon: Icon(Icons.sos_sharp,
- size: iconSize, color: Colors.black54),
+ icon: Icon(Icons.help_outline,
+ size: iconSize, color: Colors.white),
text: 'HELP',
screen: HelpScreen(),
keyName: "HelpButtonKey",
- backgroundColor: const Color(0xFFFFFFFF).withOpacity(0.30),
- ),
+ backgroundColor: Colors.red.withOpacity(0.80)),
_buildElevatedButton(
context: context,
icon: Icon(Icons.photo,
@@ -199,7 +204,6 @@ class _STMLUserDashboardScreenState extends State {
bottomNavigationBar: UiUtils.createBottomNavigationBar(context));
}
-
// Helper function to create each button for the GridView
Widget _buildElevatedButton({
required BuildContext context,
@@ -224,9 +228,7 @@ class _STMLUserDashboardScreenState extends State {
onPressed: () {
if (routeName != null) {
Navigator.pushNamed(context, routeName); // Use named route if provided
- }
- else if (screen != null)
- {
+ } else if (screen != null) {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => screen), // Default behavior
diff --git a/STML/stml_application/lib/ui/caregiver_alerts_screen.dart b/STML/stml_application/lib/ui/caregiver_alerts_screen.dart
new file mode 100644
index 00000000..1ed892b9
--- /dev/null
+++ b/STML/stml_application/lib/ui/caregiver_alerts_screen.dart
@@ -0,0 +1,268 @@
+// caregiver_alerts_screen.dart
+import 'package:flutter/material.dart';
+import 'package:cloud_firestore/cloud_firestore.dart';
+import 'package:intl/intl.dart';
+import 'package:maps_launcher/maps_launcher.dart';
+import 'package:firebase_analytics/firebase_analytics.dart';
+
+class CaregiverAlertsScreen extends StatefulWidget {
+ const CaregiverAlertsScreen({Key? key}) : super(key: key);
+
+ @override
+ State createState() => _CaregiverAlertsScreenState();
+}
+
+class _CaregiverAlertsScreenState extends State {
+ bool _isLoading = false;
+ String? _filterStatus;
+
+ @override
+ void initState() {
+ super.initState();
+ // Log screen view for analytics
+ FirebaseAnalytics.instance.logScreenView(
+ screenName: 'caregiver_alerts_screen',
+ );
+ }
+
+ Future _respondToAlert(String docId, String userId) async {
+ setState(() {
+ _isLoading = true;
+ });
+
+ try {
+ // Update status in Firestore
+ await FirebaseFirestore.instance
+ .collection('helpRequests')
+ .doc(docId)
+ .update({
+ 'status': 'responded',
+ 'responseTime': FieldValue.serverTimestamp(),
+ });
+
+ // Log the response for analytics
+ await FirebaseAnalytics.instance.logEvent(
+ name: 'emergency_alert_responded',
+ parameters: {
+ 'request_id': docId,
+ 'user_id': userId,
+ },
+ );
+
+ // Show confirmation
+ if (mounted) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(
+ content: Text('Response confirmed. The user has been notified.'),
+ backgroundColor: Colors.green,
+ ),
+ );
+ }
+ } catch (e) {
+ if (mounted) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text('Error: ${e.toString()}'),
+ backgroundColor: Colors.red,
+ ),
+ );
+ }
+ } finally {
+ if (mounted) {
+ setState(() {
+ _isLoading = false;
+ });
+ }
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('Help Requests'),
+ actions: [
+ PopupMenuButton(
+ onSelected: (value) {
+ setState(() {
+ _filterStatus = value == 'all' ? null : value;
+ });
+ },
+ itemBuilder: (BuildContext context) => [
+ const PopupMenuItem(
+ value: null,
+ child: Text('All Requests'),
+ ),
+ const PopupMenuItem(
+ value: 'sent',
+ child: Text('Pending Requests'),
+ ),
+ const PopupMenuItem(
+ value: 'responded',
+ child: Text('Responded Requests'),
+ ),
+ ],
+ icon: const Icon(Icons.filter_list),
+ ),
+ ],
+ ),
+ body: _isLoading
+ ? const Center(child: CircularProgressIndicator())
+ : StreamBuilder(
+ stream: _filterStatus == null
+ ? FirebaseFirestore.instance
+ .collection('helpRequests')
+ .orderBy('timestamp', descending: true)
+ .snapshots()
+ : FirebaseFirestore.instance
+ .collection('helpRequests')
+ .where('status', isEqualTo: _filterStatus)
+ .orderBy('timestamp', descending: true)
+ .snapshots(),
+ builder: (context, snapshot) {
+ if (snapshot.connectionState == ConnectionState.waiting) {
+ return const Center(child: CircularProgressIndicator());
+ }
+
+ if (!snapshot.hasData || snapshot.data!.docs.isEmpty) {
+ return Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ const Icon(Icons.notifications_off,
+ size: 64, color: Colors.grey),
+ const SizedBox(height: 16),
+ Text(
+ _filterStatus == null
+ ? 'No help requests found'
+ : 'No $_filterStatus requests found',
+ style:
+ const TextStyle(fontSize: 18, color: Colors.grey),
+ ),
+ ],
+ ),
+ );
+ }
+
+ return ListView.builder(
+ itemCount: snapshot.data!.docs.length,
+ itemBuilder: (context, index) {
+ var doc = snapshot.data!.docs[index];
+ var data = doc.data() as Map;
+ var userId = data['userId'] as String? ?? 'unknown';
+ var timestamp = data['timestamp'] as Timestamp?;
+ var location = data['location'] as Map?;
+ var status = data['status'] as String? ?? 'unknown';
+
+ return Card(
+ margin: const EdgeInsets.symmetric(
+ vertical: 8, horizontal: 16),
+ elevation: 4,
+ child: Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ ListTile(
+ leading: CircleAvatar(
+ backgroundColor: status == 'sent'
+ ? Colors.red
+ : Colors.green,
+ child: Icon(
+ status == 'sent'
+ ? Icons.warning
+ : Icons.check,
+ color: Colors.white,
+ ),
+ ),
+ title: Text(
+ 'Help requested by ${data['userName'] ?? 'Unknown'}',
+ style: const TextStyle(
+ fontWeight: FontWeight.bold,
+ fontSize: 16,
+ ),
+ ),
+ subtitle: Text(
+ timestamp != null
+ ? DateFormat.yMMMd()
+ .add_jm()
+ .format(timestamp.toDate())
+ : 'Unknown time',
+ style: TextStyle(
+ color: Colors.grey[600],
+ ),
+ ),
+ trailing: status == 'sent'
+ ? ElevatedButton.icon(
+ icon: const Icon(Icons.check_circle),
+ label: const Text('Respond'),
+ style: ElevatedButton.styleFrom(
+ backgroundColor: Colors.green,
+ foregroundColor: Colors.white,
+ ),
+ onPressed: () =>
+ _respondToAlert(doc.id, userId),
+ )
+ : Chip(
+ label: const Text('Responded'),
+ backgroundColor: Colors.green[100],
+ labelStyle:
+ TextStyle(color: Colors.green[800]),
+ ),
+ ),
+ if (location != null && location['address'] != null)
+ Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 16, vertical: 8),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const Text(
+ 'Location:',
+ style: TextStyle(
+ fontWeight: FontWeight.bold),
+ ),
+ Text(location['address']),
+ ],
+ ),
+ ),
+ ButtonBar(
+ alignment: MainAxisAlignment.end,
+ children: [
+ if (location != null &&
+ (location['latitude'] != null &&
+ location['longitude'] != null))
+ OutlinedButton.icon(
+ icon: const Icon(Icons.map),
+ label: const Text('Open Map'),
+ onPressed: () {
+ MapsLauncher.launchCoordinates(
+ location['latitude'],
+ location['longitude'],
+ 'Help request location',
+ );
+ },
+ ),
+ if (location != null &&
+ location['address'] != null)
+ OutlinedButton.icon(
+ icon: const Icon(Icons.navigation),
+ label: const Text('Directions'),
+ onPressed: () {
+ MapsLauncher.launchQuery(
+ location['address']);
+ },
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ );
+ },
+ );
+ },
+ ),
+ );
+ }
+}
diff --git a/STML/stml_application/lib/ui/help_screen.dart b/STML/stml_application/lib/ui/help_screen.dart
new file mode 100644
index 00000000..b32ebae3
--- /dev/null
+++ b/STML/stml_application/lib/ui/help_screen.dart
@@ -0,0 +1,329 @@
+import 'package:flutter/material.dart';
+import 'package:firebase_messaging/firebase_messaging.dart';
+import 'package:memoryminder/services/notification_service.dart';
+import 'package:memoryminder/ui/location_history_screen.dart';
+import 'package:memoryminder/src/utils/permission_manager.dart';
+import 'package:cloud_firestore/cloud_firestore.dart';
+import 'package:firebase_analytics/firebase_analytics.dart';
+import 'dart:async'; // For StreamSubscription
+
+class HelpScreen extends StatefulWidget {
+ const HelpScreen({Key? key}) : super(key: key);
+
+ @override
+ State createState() => _HelpScreenState();
+}
+
+class _HelpScreenState extends State {
+ final NotificationService _notificationService = NotificationService();
+ bool _isLoading = false;
+ bool _helpSent = false;
+ String? _requestId;
+ String _statusMessage = '';
+ StreamSubscription? _statusSubscription;
+
+ @override
+ void initState() {
+ super.initState();
+ _notificationService.initialize();
+ _checkPermissions();
+
+ // Log screen view for analytics
+ FirebaseAnalytics.instance.logScreenView(
+ screenName: 'help_screen',
+ );
+ }
+
+ @override
+ void dispose() {
+ _statusSubscription?.cancel();
+ super.dispose();
+ }
+
+ Future _checkPermissions() async {
+ // Check general permissions using the existing PermissionManager
+ await PermissionManager.requestInitialPermissions();
+
+ // Additionally check location service
+ await PermissionManager.checkIfLocationServiceIsActive(context);
+
+ // Also request notification permission (not covered in PermissionManager)
+ await _requestNotificationPermission();
+ }
+
+ Future _requestNotificationPermission() async {
+ NotificationSettings settings =
+ await FirebaseMessaging.instance.requestPermission(
+ alert: true,
+ badge: true,
+ sound: true,
+ provisional: false,
+ );
+
+ if (settings.authorizationStatus == AuthorizationStatus.denied) {
+ if (mounted) {
+ await showDialog(
+ context: context,
+ builder: (context) => AlertDialog(
+ title: const Text("Notifications Required"),
+ content: const Text(
+ "Notifications are essential for caregiver alerts. Please enable them in your device settings."),
+ actions: [
+ TextButton(
+ onPressed: () => Navigator.pop(context),
+ child: const Text("OK"),
+ ),
+ ],
+ ),
+ );
+ }
+ return false;
+ }
+
+ return settings.authorizationStatus == AuthorizationStatus.authorized;
+ }
+
+ void _subscribeToHelpRequestUpdates(String requestId) {
+ _statusSubscription?.cancel();
+
+ _statusSubscription = FirebaseFirestore.instance
+ .collection('helpRequests')
+ .doc(requestId)
+ .snapshots()
+ .listen((snapshot) {
+ if (!mounted) return;
+
+ if (snapshot.exists) {
+ final status = snapshot.data()?['status'];
+
+ setState(() {
+ if (status == 'responded') {
+ _statusMessage = 'Your caregiver has confirmed and is on the way!';
+
+ // Log status update received
+ FirebaseAnalytics.instance.logEvent(
+ name: 'help_request_status_updated',
+ parameters: {
+ 'request_id': requestId,
+ 'status': status,
+ },
+ );
+
+ // Show a prominent notification
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(
+ content: Text('Your caregiver is on the way!'),
+ backgroundColor: Colors.green,
+ duration: Duration(seconds: 10),
+ ),
+ );
+ } else {
+ _statusMessage =
+ 'Help request sent. Waiting for caregiver to respond...';
+ }
+ });
+ }
+ });
+ }
+
+ Future _sendHelpRequest() async {
+ // Check permissions before sending alert
+ bool hasGeneralPermissions =
+ await PermissionManager.requestInitialPermissions();
+ bool hasLocationService =
+ await PermissionManager.checkIfLocationServiceIsActive(context);
+ bool hasNotificationPermission = await _requestNotificationPermission();
+
+ if (!hasGeneralPermissions ||
+ !hasLocationService ||
+ !hasNotificationPermission) {
+ if (!mounted) return;
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(
+ content: Text('Required permissions are missing'),
+ backgroundColor: Colors.orange,
+ ),
+ );
+ return;
+ }
+
+ setState(() {
+ _isLoading = true;
+ _helpSent = false;
+ _statusMessage = '';
+ });
+
+ try {
+ // Get the most recent location entry
+ final locations = await LocationDatabase.instance.readAllLocations();
+ final currentLocation = locations.isNotEmpty ? locations.first : null;
+
+ // Send the help notification with location data
+ final success =
+ await _notificationService.sendHelpNotification(currentLocation);
+
+ // Get the request ID from the notification service
+ _requestId = _notificationService.lastRequestId;
+
+ if (!mounted) return;
+
+ if (success && _requestId != null) {
+ setState(() {
+ _helpSent = true;
+ _statusMessage =
+ 'Help request sent. Waiting for caregiver to respond...';
+ });
+
+ // Subscribe to updates on this request
+ _subscribeToHelpRequestUpdates(_requestId!);
+
+ // Show feedback
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(
+ content: Text('Help request sent successfully to caregiver.'),
+ backgroundColor: Colors.green,
+ ),
+ );
+ } else {
+ // Show failure message
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(
+ content: Text('Failed to send help request. Please try again.'),
+ backgroundColor: Colors.red,
+ ),
+ );
+ }
+ } catch (e) {
+ if (!mounted) return;
+
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text('Error: ${e.toString()}'),
+ backgroundColor: Colors.red,
+ ),
+ );
+ } finally {
+ if (mounted) {
+ setState(() {
+ _isLoading = false;
+ });
+ }
+ }
+ }
+
+ Widget _buildStatusIndicator() {
+ if (!_helpSent) return const SizedBox.shrink();
+
+ return Container(
+ margin: const EdgeInsets.symmetric(vertical: 20, horizontal: 24),
+ padding: const EdgeInsets.all(16),
+ decoration: BoxDecoration(
+ color: Colors.blue[50],
+ borderRadius: BorderRadius.circular(12),
+ border: Border.all(color: Colors.blue[200]!),
+ ),
+ child: Column(
+ children: [
+ Row(
+ children: [
+ Container(
+ padding: const EdgeInsets.all(8),
+ decoration: BoxDecoration(
+ color: Colors.blue[100],
+ shape: BoxShape.circle,
+ ),
+ child: const Icon(Icons.info_outline, color: Colors.blue),
+ ),
+ const SizedBox(width: 12),
+ Expanded(
+ child: Text(
+ _statusMessage,
+ style: const TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(height: 12),
+ const LinearProgressIndicator(),
+ ],
+ ),
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('Help'),
+ ),
+ body: Center(
+ child: SingleChildScrollView(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ const Icon(
+ Icons.emergency,
+ size: 80,
+ color: Colors.red,
+ ),
+ const SizedBox(height: 20),
+ const Text(
+ 'Need assistance?',
+ style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
+ ),
+ const SizedBox(height: 20),
+
+ // Status indicator (shows when help is sent)
+ _buildStatusIndicator(),
+
+ const SizedBox(height: 20),
+ _isLoading
+ ? const Column(
+ children: [
+ CircularProgressIndicator(color: Colors.red),
+ SizedBox(height: 10),
+ Text(
+ 'Sending emergency alert...',
+ style: TextStyle(color: Colors.red),
+ ),
+ ],
+ )
+ : _helpSent
+ ? ElevatedButton.icon(
+ icon: const Icon(Icons.refresh),
+ label: const Text('SEND ANOTHER ALERT'),
+ style: ElevatedButton.styleFrom(
+ backgroundColor: Colors.orange,
+ padding: const EdgeInsets.symmetric(
+ horizontal: 40, vertical: 15),
+ ),
+ onPressed: _sendHelpRequest,
+ )
+ : ElevatedButton.icon(
+ icon: const Icon(Icons.warning_amber_rounded),
+ label: const Text('SEND EMERGENCY ALERT'),
+ style: ElevatedButton.styleFrom(
+ backgroundColor: Colors.red,
+ padding: const EdgeInsets.symmetric(
+ horizontal: 40, vertical: 15),
+ ),
+ onPressed: _sendHelpRequest,
+ ),
+ const SizedBox(height: 20),
+ const Text(
+ 'This will send your current location to your caregiver',
+ textAlign: TextAlign.center,
+ style: TextStyle(color: Colors.grey),
+ ),
+
+ if (_helpSent) const SizedBox(height: 40),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/STML/stml_application/linux/flutter/generated_plugin_registrant.cc b/STML/stml_application/linux/flutter/generated_plugin_registrant.cc
index 3ccd5513..ba1e23e1 100644
--- a/STML/stml_application/linux/flutter/generated_plugin_registrant.cc
+++ b/STML/stml_application/linux/flutter/generated_plugin_registrant.cc
@@ -8,6 +8,7 @@
#include
#include
+#include
#include
void fl_register_plugins(FlPluginRegistry* registry) {
@@ -17,6 +18,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
+ g_autoptr(FlPluginRegistrar) maps_launcher_registrar =
+ fl_plugin_registry_get_registrar_for_plugin(registry, "MapsLauncherPlugin");
+ maps_launcher_plugin_register_with_registrar(maps_launcher_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
diff --git a/STML/stml_application/linux/flutter/generated_plugins.cmake b/STML/stml_application/linux/flutter/generated_plugins.cmake
index 9ce94c49..d8e70d24 100644
--- a/STML/stml_application/linux/flutter/generated_plugins.cmake
+++ b/STML/stml_application/linux/flutter/generated_plugins.cmake
@@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
file_selector_linux
flutter_secure_storage_linux
+ maps_launcher
url_launcher_linux
)
diff --git a/STML/stml_application/macos/Flutter/GeneratedPluginRegistrant.swift b/STML/stml_application/macos/Flutter/GeneratedPluginRegistrant.swift
index 20fb3502..f2302e6c 100644
--- a/STML/stml_application/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/STML/stml_application/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -6,7 +6,9 @@ import FlutterMacOS
import Foundation
import cloud_firestore
+import cloud_functions
import file_selector_macos
+import firebase_analytics
import firebase_auth
import firebase_core
import firebase_messaging
@@ -16,6 +18,7 @@ import flutter_tts
import flutter_web_auth
import geolocator_apple
import local_auth_darwin
+import maps_launcher
import package_info_plus
import path_provider_foundation
import speech_to_text
@@ -26,7 +29,9 @@ import wakelock_plus
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin"))
+ FLTFirebaseFunctionsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFunctionsPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
+ FLTFirebaseAnalyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAnalyticsPlugin"))
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
@@ -36,6 +41,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterWebAuthPlugin.register(with: registry.registrar(forPlugin: "FlutterWebAuthPlugin"))
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
FLALocalAuthPlugin.register(with: registry.registrar(forPlugin: "FLALocalAuthPlugin"))
+ MapsLauncherPlugin.register(with: registry.registrar(forPlugin: "MapsLauncherPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SpeechToTextPlugin.register(with: registry.registrar(forPlugin: "SpeechToTextPlugin"))
diff --git a/STML/stml_application/pubspec.yaml b/STML/stml_application/pubspec.yaml
index 279902fd..618c50ac 100644
--- a/STML/stml_application/pubspec.yaml
+++ b/STML/stml_application/pubspec.yaml
@@ -42,7 +42,7 @@ dependencies:
timeago: ^3.5.0
path_provider: ^2.1.1
video_thumbnail: ^0.5.3
- flutter_dotenv: ^5.1.0
+ flutter_dotenv: ^5.2.1
aws_rekognition_api: ^2.0.0
aws_s3_api: ^2.0.0
camera: ^0.11.1
@@ -66,9 +66,13 @@ dependencies:
cloud_firestore: ^5.6.5
flutter_local_notifications: ^18.0.1
fitbitter: ^2.0.4
- flutter_secure_storage: ^8.0.0
+ flutter_secure_storage: 8.1.0
http: ^0.13.6
fl_chart: ^0.64.0
+ cloud_functions: ^5.3.4
+ maps_launcher: ^3.0.0+1
+ intl: ^0.19.0
+ firebase_analytics: ^11.4.4
dev_dependencies:
flutter_test:
diff --git a/STML/stml_application/windows/flutter/generated_plugin_registrant.cc b/STML/stml_application/windows/flutter/generated_plugin_registrant.cc
index 14cd0546..8e6e6414 100644
--- a/STML/stml_application/windows/flutter/generated_plugin_registrant.cc
+++ b/STML/stml_application/windows/flutter/generated_plugin_registrant.cc
@@ -14,6 +14,7 @@
#include
#include
#include
+#include
#include
#include
@@ -34,6 +35,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("GeolocatorWindows"));
LocalAuthPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("LocalAuthPlugin"));
+ MapsLauncherPluginRegisterWithRegistrar(
+ registry->GetRegistrarForPlugin("MapsLauncherPlugin"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
UrlLauncherWindowsRegisterWithRegistrar(
diff --git a/STML/stml_application/windows/flutter/generated_plugins.cmake b/STML/stml_application/windows/flutter/generated_plugins.cmake
index 03daf122..348c6bb2 100644
--- a/STML/stml_application/windows/flutter/generated_plugins.cmake
+++ b/STML/stml_application/windows/flutter/generated_plugins.cmake
@@ -11,6 +11,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
flutter_tts
geolocator_windows
local_auth_windows
+ maps_launcher
permission_handler_windows
url_launcher_windows
)
diff --git a/firebase.json b/firebase.json
new file mode 100644
index 00000000..5f923501
--- /dev/null
+++ b/firebase.json
@@ -0,0 +1,18 @@
+{
+ "functions": [
+ {
+ "source": "functions",
+ "codebase": "default",
+ "ignore": [
+ "node_modules",
+ ".git",
+ "firebase-debug.log",
+ "firebase-debug.*.log",
+ "*.local"
+ ],
+ "predeploy": [
+ "npm --prefix \"$RESOURCE_DIR\" run lint"
+ ]
+ }
+ ]
+}
diff --git a/functions/.eslintrc.json b/functions/.eslintrc.json
new file mode 100644
index 00000000..b84faaa0
--- /dev/null
+++ b/functions/.eslintrc.json
@@ -0,0 +1,9 @@
+{
+ "root": true,
+ "env": {
+ "es6": true,
+ "node": true
+ },
+ "extends": [],
+ "rules": {}
+}
\ No newline at end of file
diff --git a/functions/.gitignore b/functions/.gitignore
new file mode 100644
index 00000000..21ee8d3d
--- /dev/null
+++ b/functions/.gitignore
@@ -0,0 +1,2 @@
+node_modules/
+*.local
\ No newline at end of file
diff --git a/functions/index.js b/functions/index.js
new file mode 100644
index 00000000..bd6d04db
--- /dev/null
+++ b/functions/index.js
@@ -0,0 +1,38 @@
+const functions = require("firebase-functions");
+const admin = require("firebase-admin");
+admin.initializeApp();
+
+exports.sendHelpAlert = functions.https.onCall(async (data, context) => {
+ const {caregiverToken, location, userId, userName} = data;
+
+ if (!caregiverToken) {
+ return {success: false, error: "Token du caregiver manquant"};
+ }
+
+ try {
+ const latitude = location && location.latitude ? location.latitude.toString() : "";
+ const longitude = location && location.longitude ? location.longitude.toString() : "";
+ const address = location && location.address ? location.address : "";
+
+ await admin.messaging().send({
+ token: caregiverToken,
+ notification: {
+ title: "URGENT: Help Needed!",
+ body: `${userName || "A user"} needs help immediately!`
+ },
+ data: {
+ type: "help_request",
+ userId: userId || "",
+ latitude: latitude,
+ longitude: longitude,
+ address: address,
+ timestamp: new Date().toISOString()
+ }
+ });
+
+ return {success: true};
+ } catch (error) {
+ console.error("Error sending message:", error);
+ return {success: false, error: error.message};
+ }
+});
\ No newline at end of file
diff --git a/functions/package-lock.json b/functions/package-lock.json
new file mode 100644
index 00000000..684b2b87
--- /dev/null
+++ b/functions/package-lock.json
@@ -0,0 +1,7142 @@
+{
+ "name": "functions",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "functions",
+ "dependencies": {
+ "firebase-admin": "^12.6.0",
+ "firebase-functions": "^6.0.1"
+ },
+ "devDependencies": {
+ "eslint": "^8.15.0",
+ "eslint-config-google": "^0.14.0",
+ "firebase-functions-test": "^3.1.0"
+ },
+ "engines": {
+ "node": "22"
+ }
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.26.2",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
+ "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.25.9",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.26.8",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz",
+ "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.26.10",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz",
+ "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.0",
+ "@babel/code-frame": "^7.26.2",
+ "@babel/generator": "^7.26.10",
+ "@babel/helper-compilation-targets": "^7.26.5",
+ "@babel/helper-module-transforms": "^7.26.0",
+ "@babel/helpers": "^7.26.10",
+ "@babel/parser": "^7.26.10",
+ "@babel/template": "^7.26.9",
+ "@babel/traverse": "^7.26.10",
+ "@babel/types": "^7.26.10",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz",
+ "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/parser": "^7.27.0",
+ "@babel/types": "^7.27.0",
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz",
+ "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/compat-data": "^7.26.8",
+ "@babel/helper-validator-option": "^7.25.9",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz",
+ "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/traverse": "^7.25.9",
+ "@babel/types": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.26.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz",
+ "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.25.9",
+ "@babel/traverse": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.26.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz",
+ "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
+ "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
+ "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz",
+ "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz",
+ "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/template": "^7.27.0",
+ "@babel/types": "^7.27.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz",
+ "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/types": "^7.27.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-async-generators": {
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
+ "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-bigint": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz",
+ "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-class-properties": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
+ "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.12.13"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-class-static-block": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz",
+ "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-attributes": {
+ "version": "7.26.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz",
+ "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-meta": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
+ "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-json-strings": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
+ "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-jsx": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz",
+ "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-logical-assignment-operators": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
+ "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
+ "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-numeric-separator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
+ "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-object-rest-spread": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
+ "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-catch-binding": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
+ "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-chaining": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
+ "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-private-property-in-object": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz",
+ "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-top-level-await": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
+ "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-typescript": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz",
+ "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz",
+ "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.26.2",
+ "@babel/parser": "^7.27.0",
+ "@babel/types": "^7.27.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz",
+ "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.26.2",
+ "@babel/generator": "^7.27.0",
+ "@babel/parser": "^7.27.0",
+ "@babel/template": "^7.27.0",
+ "@babel/types": "^7.27.0",
+ "debug": "^4.3.1",
+ "globals": "^11.1.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse/node_modules/globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz",
+ "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@bcoe/v8-coverage": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
+ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz",
+ "integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
+ "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.6.0",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
+ "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@fastify/busboy": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.1.1.tgz",
+ "integrity": "sha512-5DGmA8FTdB2XbDeEwc/5ZXBl6UbBAyBOOLlPuBnZ/N1SwdH9Ii+cOX3tBROlDgcTXxjOYnLMVoKk9+FXAw0CJw==",
+ "license": "MIT"
+ },
+ "node_modules/@firebase/app-check-interop-types": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.2.tgz",
+ "integrity": "sha512-LMs47Vinv2HBMZi49C09dJxp0QT5LwDzFaVGf/+ITHe3BlIhUiLNttkATSXplc89A2lAaeTqjgqVkiRfUGyQiQ==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@firebase/app-types": {
+ "version": "0.9.2",
+ "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.2.tgz",
+ "integrity": "sha512-oMEZ1TDlBz479lmABwWsWjzHwheQKiAgnuKxE0pz0IXCVx7/rtlkx1fQ6GfgK24WCrxDKMplZrT50Kh04iMbXQ==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@firebase/auth-interop-types": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.3.tgz",
+ "integrity": "sha512-Fc9wuJGgxoxQeavybiuwgyi+0rssr76b+nHpj+eGhXFYAdudMWyfBHvFL/I5fEHniUM/UQdFzi9VXJK2iZF7FQ==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@firebase/component": {
+ "version": "0.6.9",
+ "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.9.tgz",
+ "integrity": "sha512-gm8EUEJE/fEac86AvHn8Z/QW8BvR56TBw3hMW0O838J/1mThYQXAIQBgUv75EqlCZfdawpWLrKt1uXvp9ciK3Q==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/util": "1.10.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@firebase/database": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.8.tgz",
+ "integrity": "sha512-dzXALZeBI1U5TXt6619cv0+tgEhJiwlUtQ55WNZY7vGAjv7Q1QioV969iYwt1AQQ0ovHnEW0YW9TiBfefLvErg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/app-check-interop-types": "0.3.2",
+ "@firebase/auth-interop-types": "0.2.3",
+ "@firebase/component": "0.6.9",
+ "@firebase/logger": "0.4.2",
+ "@firebase/util": "1.10.0",
+ "faye-websocket": "0.11.4",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@firebase/database-compat": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-1.0.8.tgz",
+ "integrity": "sha512-OpeWZoPE3sGIRPBKYnW9wLad25RaWbGyk7fFQe4xnJQKRzlynWeFBSRRAoLE2Old01WXwskUiucNqUUVlFsceg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/component": "0.6.9",
+ "@firebase/database": "1.0.8",
+ "@firebase/database-types": "1.0.5",
+ "@firebase/logger": "0.4.2",
+ "@firebase/util": "1.10.0",
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@firebase/database-types": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.5.tgz",
+ "integrity": "sha512-fTlqCNwFYyq/C6W7AJ5OCuq5CeZuBEsEwptnVxlNPkWCo5cTTyukzAHRSO/jaQcItz33FfYrrFk1SJofcu2AaQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/app-types": "0.9.2",
+ "@firebase/util": "1.10.0"
+ }
+ },
+ "node_modules/@firebase/logger": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.2.tgz",
+ "integrity": "sha512-Q1VuA5M1Gjqrwom6I6NUU4lQXdo9IAQieXlujeHZWvRt1b7qQ0KwBaNAjgxG27jgF9/mUwsNmO8ptBCGVYhB0A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@firebase/util": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.10.0.tgz",
+ "integrity": "sha512-xKtx4A668icQqoANRxyDLBLz51TAbDP9KRfpbKGxiCAW346d0BeJe5vN6/hKxxmWwnZ0mautyv39JxviwwQMOQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/@google-cloud/firestore": {
+ "version": "7.11.0",
+ "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.11.0.tgz",
+ "integrity": "sha512-88uZ+jLsp1aVMj7gh3EKYH1aulTAMFAp8sH/v5a9w8q8iqSG27RiWLoxSAFr/XocZ9hGiWH1kEnBw+zl3xAgNA==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "@opentelemetry/api": "^1.3.0",
+ "fast-deep-equal": "^3.1.1",
+ "functional-red-black-tree": "^1.0.1",
+ "google-gax": "^4.3.3",
+ "protobufjs": "^7.2.6"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@google-cloud/paginator": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz",
+ "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "arrify": "^2.0.0",
+ "extend": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@google-cloud/projectify": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz",
+ "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@google-cloud/promisify": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.1.0.tgz",
+ "integrity": "sha512-G/FQx5cE/+DqBbOpA5jKsegGwdPniU6PuIEMt+qxWgFxvxuFOzVmp6zYchtYuwAWV5/8Dgs0yAmjvNZv3uXLQg==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@google-cloud/storage": {
+ "version": "7.15.2",
+ "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.15.2.tgz",
+ "integrity": "sha512-+2k+mcQBb9zkaXMllf2wwR/rI07guAx+eZLWsGTDihW2lJRGfiqB7xu1r7/s4uvSP/T+nAumvzT5TTscwHKJ9A==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "@google-cloud/paginator": "^5.0.0",
+ "@google-cloud/projectify": "^4.0.0",
+ "@google-cloud/promisify": "^4.0.0",
+ "abort-controller": "^3.0.0",
+ "async-retry": "^1.3.3",
+ "duplexify": "^4.1.3",
+ "fast-xml-parser": "^4.4.1",
+ "gaxios": "^6.0.2",
+ "google-auth-library": "^9.6.3",
+ "html-entities": "^2.5.2",
+ "mime": "^3.0.0",
+ "p-limit": "^3.0.1",
+ "retry-request": "^7.0.0",
+ "teeny-request": "^9.0.0",
+ "uuid": "^8.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@google-cloud/storage/node_modules/uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "license": "MIT",
+ "optional": true,
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/@grpc/grpc-js": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.2.tgz",
+ "integrity": "sha512-nnR5nmL6lxF8YBqb6gWvEgLdLh/Fn+kvAdX5hUOnt48sNSb0riz/93ASd2E5gvanPA41X6Yp25bIfGRp1SMb2g==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "@grpc/proto-loader": "^0.7.13",
+ "@js-sdsl/ordered-map": "^4.4.2"
+ },
+ "engines": {
+ "node": ">=12.10.0"
+ }
+ },
+ "node_modules/@grpc/proto-loader": {
+ "version": "0.7.13",
+ "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz",
+ "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "lodash.camelcase": "^4.3.0",
+ "long": "^5.0.0",
+ "protobufjs": "^7.2.5",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
+ "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
+ "deprecated": "Use @eslint/config-array instead",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^2.0.3",
+ "debug": "^4.3.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
+ "deprecated": "Use @eslint/object-schema instead",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@istanbuljs/load-nyc-config": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
+ "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
+ "dev": true,
+ "license": "ISC",
+ "peer": true,
+ "dependencies": {
+ "camelcase": "^5.3.1",
+ "find-up": "^4.1.0",
+ "get-package-type": "^0.1.0",
+ "js-yaml": "^3.13.1",
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+ "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/schema": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
+ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@jest/console": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz",
+ "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/core": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz",
+ "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jest/console": "^29.7.0",
+ "@jest/reporters": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "ansi-escapes": "^4.2.1",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "exit": "^0.1.2",
+ "graceful-fs": "^4.2.9",
+ "jest-changed-files": "^29.7.0",
+ "jest-config": "^29.7.0",
+ "jest-haste-map": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.7.0",
+ "jest-resolve-dependencies": "^29.7.0",
+ "jest-runner": "^29.7.0",
+ "jest-runtime": "^29.7.0",
+ "jest-snapshot": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "jest-watcher": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "pretty-format": "^29.7.0",
+ "slash": "^3.0.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jest/environment": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz",
+ "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "jest-mock": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/expect": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz",
+ "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "expect": "^29.7.0",
+ "jest-snapshot": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/expect-utils": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz",
+ "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "jest-get-type": "^29.6.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/fake-timers": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz",
+ "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@sinonjs/fake-timers": "^10.0.2",
+ "@types/node": "*",
+ "jest-message-util": "^29.7.0",
+ "jest-mock": "^29.7.0",
+ "jest-util": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/globals": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz",
+ "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jest/environment": "^29.7.0",
+ "@jest/expect": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "jest-mock": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/reporters": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz",
+ "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@bcoe/v8-coverage": "^0.2.3",
+ "@jest/console": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "collect-v8-coverage": "^1.0.0",
+ "exit": "^0.1.2",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "istanbul-lib-coverage": "^3.0.0",
+ "istanbul-lib-instrument": "^6.0.0",
+ "istanbul-lib-report": "^3.0.0",
+ "istanbul-lib-source-maps": "^4.0.0",
+ "istanbul-reports": "^3.1.3",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-worker": "^29.7.0",
+ "slash": "^3.0.0",
+ "string-length": "^4.0.1",
+ "strip-ansi": "^6.0.0",
+ "v8-to-istanbul": "^9.0.1"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jest/schemas": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
+ "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@sinclair/typebox": "^0.27.8"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/source-map": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz",
+ "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "callsites": "^3.0.0",
+ "graceful-fs": "^4.2.9"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/test-result": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz",
+ "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jest/console": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/istanbul-lib-coverage": "^2.0.0",
+ "collect-v8-coverage": "^1.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/test-sequencer": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz",
+ "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jest/test-result": "^29.7.0",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/transform": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz",
+ "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/core": "^7.11.6",
+ "@jest/types": "^29.6.3",
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "babel-plugin-istanbul": "^6.1.1",
+ "chalk": "^4.0.0",
+ "convert-source-map": "^2.0.0",
+ "fast-json-stable-stringify": "^2.1.0",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "jest-regex-util": "^29.6.3",
+ "jest-util": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "pirates": "^4.0.4",
+ "slash": "^3.0.0",
+ "write-file-atomic": "^4.0.2"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/types": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
+ "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "@types/istanbul-lib-coverage": "^2.0.0",
+ "@types/istanbul-reports": "^3.0.0",
+ "@types/node": "*",
+ "@types/yargs": "^17.0.8",
+ "chalk": "^4.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.8",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
+ "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jridgewell/set-array": "^1.2.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.25",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@js-sdsl/ordered-map": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz",
+ "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==",
+ "license": "MIT",
+ "optional": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/js-sdsl"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@opentelemetry/api": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
+ "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/@protobufjs/aspromise": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
+ "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/base64": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
+ "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/codegen": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
+ "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/eventemitter": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
+ "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/fetch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
+ "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.1",
+ "@protobufjs/inquire": "^1.1.0"
+ }
+ },
+ "node_modules/@protobufjs/float": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
+ "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/inquire": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
+ "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/path": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
+ "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/pool": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
+ "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/utf8": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
+ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@sinclair/typebox": {
+ "version": "0.27.8",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
+ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@sinonjs/commons": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz",
+ "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "dependencies": {
+ "type-detect": "4.0.8"
+ }
+ },
+ "node_modules/@sinonjs/fake-timers": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz",
+ "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "dependencies": {
+ "@sinonjs/commons": "^3.0.0"
+ }
+ },
+ "node_modules/@tootallnate/once": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
+ "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.6.8",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz",
+ "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.20.7",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz",
+ "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/types": "^7.20.7"
+ }
+ },
+ "node_modules/@types/body-parser": {
+ "version": "1.19.5",
+ "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
+ "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/connect": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/caseless": {
+ "version": "0.12.5",
+ "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz",
+ "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/@types/connect": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
+ "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/cors": {
+ "version": "2.8.17",
+ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
+ "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/express": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",
+ "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^4.17.33",
+ "@types/qs": "*",
+ "@types/serve-static": "*"
+ }
+ },
+ "node_modules/@types/express-serve-static-core": {
+ "version": "4.19.6",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz",
+ "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*",
+ "@types/send": "*"
+ }
+ },
+ "node_modules/@types/graceful-fs": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
+ "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/http-errors": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
+ "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/istanbul-lib-coverage": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
+ "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/istanbul-lib-report": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
+ "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/istanbul-lib-coverage": "*"
+ }
+ },
+ "node_modules/@types/istanbul-reports": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
+ "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/istanbul-lib-report": "*"
+ }
+ },
+ "node_modules/@types/jsonwebtoken": {
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.9.tgz",
+ "integrity": "sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/ms": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/lodash": {
+ "version": "4.17.16",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.16.tgz",
+ "integrity": "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/long": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
+ "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/@types/mime": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
+ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
+ "license": "MIT"
+ },
+ "node_modules/@types/ms": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "22.13.14",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.14.tgz",
+ "integrity": "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.20.0"
+ }
+ },
+ "node_modules/@types/qs": {
+ "version": "6.9.18",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz",
+ "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/range-parser": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
+ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
+ "license": "MIT"
+ },
+ "node_modules/@types/request": {
+ "version": "2.48.12",
+ "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz",
+ "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@types/caseless": "*",
+ "@types/node": "*",
+ "@types/tough-cookie": "*",
+ "form-data": "^2.5.0"
+ }
+ },
+ "node_modules/@types/send": {
+ "version": "0.17.4",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
+ "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mime": "^1",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/serve-static": {
+ "version": "1.15.7",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
+ "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-errors": "*",
+ "@types/node": "*",
+ "@types/send": "*"
+ }
+ },
+ "node_modules/@types/stack-utils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
+ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/tough-cookie": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
+ "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/@types/yargs": {
+ "version": "17.0.33",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
+ "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/yargs-parser": "*"
+ }
+ },
+ "node_modules/@types/yargs-parser": {
+ "version": "21.0.3",
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
+ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/abort-controller": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
+ "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "event-target-shim": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=6.5"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.14.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
+ "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
+ "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-escapes": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "type-fest": "^0.21.3"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-escapes/node_modules/type-fest": {
+ "version": "0.21.3",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "peer": true,
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+ "license": "MIT"
+ },
+ "node_modules/arrify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
+ "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/async-retry": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz",
+ "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "retry": "0.13.1"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/babel-jest": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
+ "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jest/transform": "^29.7.0",
+ "@types/babel__core": "^7.1.14",
+ "babel-plugin-istanbul": "^6.1.1",
+ "babel-preset-jest": "^29.6.3",
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.9",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.8.0"
+ }
+ },
+ "node_modules/babel-plugin-istanbul": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz",
+ "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@istanbuljs/load-nyc-config": "^1.0.0",
+ "@istanbuljs/schema": "^0.1.2",
+ "istanbul-lib-instrument": "^5.0.4",
+ "test-exclude": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz",
+ "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "dependencies": {
+ "@babel/core": "^7.12.3",
+ "@babel/parser": "^7.14.7",
+ "@istanbuljs/schema": "^0.1.2",
+ "istanbul-lib-coverage": "^3.2.0",
+ "semver": "^6.3.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/babel-plugin-jest-hoist": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz",
+ "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/template": "^7.3.3",
+ "@babel/types": "^7.3.3",
+ "@types/babel__core": "^7.1.14",
+ "@types/babel__traverse": "^7.0.6"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/babel-preset-current-node-syntax": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz",
+ "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/plugin-syntax-async-generators": "^7.8.4",
+ "@babel/plugin-syntax-bigint": "^7.8.3",
+ "@babel/plugin-syntax-class-properties": "^7.12.13",
+ "@babel/plugin-syntax-class-static-block": "^7.14.5",
+ "@babel/plugin-syntax-import-attributes": "^7.24.7",
+ "@babel/plugin-syntax-import-meta": "^7.10.4",
+ "@babel/plugin-syntax-json-strings": "^7.8.3",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.3",
+ "@babel/plugin-syntax-private-property-in-object": "^7.14.5",
+ "@babel/plugin-syntax-top-level-await": "^7.14.5"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/babel-preset-jest": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz",
+ "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "babel-plugin-jest-hoist": "^29.6.3",
+ "babel-preset-current-node-syntax": "^1.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/bignumber.js": {
+ "version": "9.1.2",
+ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz",
+ "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.3",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
+ "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "on-finished": "2.4.1",
+ "qs": "6.13.0",
+ "raw-body": "2.5.2",
+ "type-is": "~1.6.18",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/body-parser/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/body-parser/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.24.4",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz",
+ "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001688",
+ "electron-to-chromium": "^1.5.73",
+ "node-releases": "^2.0.19",
+ "update-browserslist-db": "^1.1.1"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/bser": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
+ "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "node-int64": "^0.4.0"
+ }
+ },
+ "node_modules/buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001707",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001707.tgz",
+ "integrity": "sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0",
+ "peer": true
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/char-regex": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
+ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ci-info": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
+ "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cjs-module-lexer": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz",
+ "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "devOptional": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/co": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "iojs": ">= 1.0.0",
+ "node": ">= 0.12.0"
+ }
+ },
+ "node_modules/collect-v8-coverage": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz",
+ "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/cookie": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
+ "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
+ "license": "MIT"
+ },
+ "node_modules/cors": {
+ "version": "2.8.5",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/create-jest": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz",
+ "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "chalk": "^4.0.0",
+ "exit": "^0.1.2",
+ "graceful-fs": "^4.2.9",
+ "jest-config": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "prompts": "^2.0.1"
+ },
+ "bin": {
+ "create-jest": "bin/create-jest.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
+ "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/dedent": {
+ "version": "1.5.3",
+ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz",
+ "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "peerDependencies": {
+ "babel-plugin-macros": "^3.1.0"
+ },
+ "peerDependenciesMeta": {
+ "babel-plugin-macros": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/detect-newline": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
+ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/diff-sequences": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
+ "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/duplexify": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz",
+ "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "end-of-stream": "^1.4.1",
+ "inherits": "^2.0.3",
+ "readable-stream": "^3.1.1",
+ "stream-shift": "^1.0.2"
+ }
+ },
+ "node_modules/ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.126",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.126.tgz",
+ "integrity": "sha512-AtH1uLcTC72LA4vfYcEJJkrMk/MY/X0ub8Hv7QGAePW2JkeUFHEL/QfS4J77R6M87Sss8O0OcqReSaN1bpyA+Q==",
+ "dev": true,
+ "license": "ISC",
+ "peer": true
+ },
+ "node_modules/emittery": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
+ "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/emittery?sponsor=1"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/end-of-stream": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+ "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
+ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
+ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.4",
+ "@eslint/js": "8.57.1",
+ "@humanwhocodes/config-array": "^0.13.0",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "@ungap/structured-clone": "^1.2.0",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.2.2",
+ "eslint-visitor-keys": "^3.4.3",
+ "espree": "^9.6.1",
+ "esquery": "^1.4.2",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3",
+ "strip-ansi": "^6.0.1",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-config-google": {
+ "version": "0.14.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-google/-/eslint-config-google-0.14.0.tgz",
+ "integrity": "sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "peerDependencies": {
+ "eslint": ">=5.16.0"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.9.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "peer": true,
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/event-target-shim": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
+ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/exit": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
+ "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/expect": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz",
+ "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jest/expect-utils": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "jest-matcher-utils": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
+ "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.20.3",
+ "content-disposition": "0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "0.7.1",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "1.3.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "merge-descriptors": "1.0.3",
+ "methods": "~1.1.2",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "0.1.12",
+ "proxy-addr": "~2.0.7",
+ "qs": "6.13.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "0.19.0",
+ "serve-static": "1.16.2",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/express/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/farmhash-modern": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/farmhash-modern/-/farmhash-modern-1.1.0.tgz",
+ "integrity": "sha512-6ypT4XfgqJk/F3Yuv4SX26I3doUjt0GTG4a+JgWxXQpxXzTBq8fPUeGHfcYMMDPHJHm3yPOSjaeBwBGAHWXCdA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-xml-parser": {
+ "version": "4.5.3",
+ "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz",
+ "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/NaturalIntelligence"
+ }
+ ],
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "strnum": "^1.1.1"
+ },
+ "bin": {
+ "fxparser": "src/cli/cli.js"
+ }
+ },
+ "node_modules/fastq": {
+ "version": "1.19.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
+ "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/faye-websocket": {
+ "version": "0.11.4",
+ "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz",
+ "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "websocket-driver": ">=0.5.1"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/fb-watchman": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
+ "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "bser": "2.1.1"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
+ "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "2.0.1",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/finalhandler/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/finalhandler/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/firebase-admin": {
+ "version": "12.7.0",
+ "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-12.7.0.tgz",
+ "integrity": "sha512-raFIrOyTqREbyXsNkSHyciQLfv8AUZazehPaQS1lZBSCDYW74FYXU0nQZa3qHI4K+hawohlDbywZ4+qce9YNxA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@fastify/busboy": "^3.0.0",
+ "@firebase/database-compat": "1.0.8",
+ "@firebase/database-types": "1.0.5",
+ "@types/node": "^22.0.1",
+ "farmhash-modern": "^1.1.0",
+ "jsonwebtoken": "^9.0.0",
+ "jwks-rsa": "^3.1.0",
+ "node-forge": "^1.3.1",
+ "uuid": "^10.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "optionalDependencies": {
+ "@google-cloud/firestore": "^7.7.0",
+ "@google-cloud/storage": "^7.7.0"
+ }
+ },
+ "node_modules/firebase-functions": {
+ "version": "6.3.2",
+ "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-6.3.2.tgz",
+ "integrity": "sha512-FC3A1/nhqt1ZzxRnj5HZLScQaozAcFSD/vSR8khqSoFNOfxuXgwJS6ZABTB7+v+iMD5z6Mmxw6OfqITUBuI7OQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/cors": "^2.8.5",
+ "@types/express": "^4.17.21",
+ "cors": "^2.8.5",
+ "express": "^4.21.0",
+ "protobufjs": "^7.2.2"
+ },
+ "bin": {
+ "firebase-functions": "lib/bin/firebase-functions.js"
+ },
+ "engines": {
+ "node": ">=14.10.0"
+ },
+ "peerDependencies": {
+ "firebase-admin": "^11.10.0 || ^12.0.0 || ^13.0.0"
+ }
+ },
+ "node_modules/firebase-functions-test": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/firebase-functions-test/-/firebase-functions-test-3.4.1.tgz",
+ "integrity": "sha512-qAq0oszrBGdf4bnCF6t4FoSgMsepeIXh0Pi/FhikSE6e+TvKKGpfrfUP/5pFjJZxFcLsweoau88KydCql4xSeg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/lodash": "^4.14.104",
+ "lodash": "^4.17.5",
+ "ts-deepmerge": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "firebase-admin": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0",
+ "firebase-functions": ">=4.9.0",
+ "jest": ">=28.0.0"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
+ "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.3",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/form-data": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.3.tgz",
+ "integrity": "sha512-XHIrMD0NpDrNM/Ckf7XJiBbLl57KEhT3+i3yY+eWm+cqYZJQTZrKo8Y8AWKnuV5GT4scfuUGt9LzNoIx3dU1nQ==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "mime-types": "^2.1.35",
+ "safe-buffer": "^5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.12"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/functional-red-black-tree": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+ "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/gaxios": {
+ "version": "6.7.1",
+ "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz",
+ "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "extend": "^3.0.2",
+ "https-proxy-agent": "^7.0.1",
+ "is-stream": "^2.0.0",
+ "node-fetch": "^2.6.9",
+ "uuid": "^9.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/gaxios/node_modules/uuid": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
+ "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/gcp-metadata": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz",
+ "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "gaxios": "^6.1.1",
+ "google-logging-utils": "^0.0.2",
+ "json-bigint": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "devOptional": true,
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-package-type": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
+ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "13.24.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/google-auth-library": {
+ "version": "9.15.1",
+ "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz",
+ "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "base64-js": "^1.3.0",
+ "ecdsa-sig-formatter": "^1.0.11",
+ "gaxios": "^6.1.1",
+ "gcp-metadata": "^6.1.0",
+ "gtoken": "^7.0.0",
+ "jws": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/google-gax": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.4.1.tgz",
+ "integrity": "sha512-Phyp9fMfA00J3sZbJxbbB4jC55b7DBjE3F6poyL3wKMEBVKA79q6BGuHcTiM28yOzVql0NDbRL8MLLh8Iwk9Dg==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "@grpc/grpc-js": "^1.10.9",
+ "@grpc/proto-loader": "^0.7.13",
+ "@types/long": "^4.0.0",
+ "abort-controller": "^3.0.0",
+ "duplexify": "^4.0.0",
+ "google-auth-library": "^9.3.0",
+ "node-fetch": "^2.7.0",
+ "object-hash": "^3.0.0",
+ "proto3-json-serializer": "^2.0.2",
+ "protobufjs": "^7.3.2",
+ "retry-request": "^7.0.0",
+ "uuid": "^9.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/google-gax/node_modules/uuid": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
+ "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/google-logging-utils": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz",
+ "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC",
+ "peer": true
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/gtoken": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz",
+ "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "gaxios": "^6.0.0",
+ "jws": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/html-entities": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.3.tgz",
+ "integrity": "sha512-D3AfvN7SjhTgBSA8L1BN4FpPzuEd06uy4lHwSoRWr0lndi9BKaNzPLKGOWZ2ocSGguozr08TTb2jhCLHaemruw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/mdevils"
+ },
+ {
+ "type": "patreon",
+ "url": "https://patreon.com/mdevils"
+ }
+ ],
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/html-escaper": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
+ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/http-parser-js": {
+ "version": "0.5.9",
+ "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.9.tgz",
+ "integrity": "sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw==",
+ "license": "MIT"
+ },
+ "node_modules/http-proxy-agent": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
+ "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@tootallnate/once": "2",
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/http-proxy-agent/node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "engines": {
+ "node": ">=10.17.0"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/import-local": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
+ "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "pkg-dir": "^4.2.0",
+ "resolve-cwd": "^3.0.0"
+ },
+ "bin": {
+ "import-local-fixture": "fixtures/cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-generator-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz",
+ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/istanbul-lib-coverage": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
+ "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/istanbul-lib-instrument": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz",
+ "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "dependencies": {
+ "@babel/core": "^7.23.9",
+ "@babel/parser": "^7.23.9",
+ "@istanbuljs/schema": "^0.1.3",
+ "istanbul-lib-coverage": "^3.2.0",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-instrument/node_modules/semver": {
+ "version": "7.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
+ "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
+ "dev": true,
+ "license": "ISC",
+ "peer": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-report": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
+ "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "dependencies": {
+ "istanbul-lib-coverage": "^3.0.0",
+ "make-dir": "^4.0.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-source-maps": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz",
+ "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "dependencies": {
+ "debug": "^4.1.1",
+ "istanbul-lib-coverage": "^3.0.0",
+ "source-map": "^0.6.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-reports": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
+ "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "dependencies": {
+ "html-escaper": "^2.0.0",
+ "istanbul-lib-report": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jest": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
+ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jest/core": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "import-local": "^3.0.2",
+ "jest-cli": "^29.7.0"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-changed-files": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz",
+ "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "execa": "^5.0.0",
+ "jest-util": "^29.7.0",
+ "p-limit": "^3.1.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-circus": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz",
+ "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jest/environment": "^29.7.0",
+ "@jest/expect": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "co": "^4.6.0",
+ "dedent": "^1.0.0",
+ "is-generator-fn": "^2.0.0",
+ "jest-each": "^29.7.0",
+ "jest-matcher-utils": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-runtime": "^29.7.0",
+ "jest-snapshot": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "p-limit": "^3.1.0",
+ "pretty-format": "^29.7.0",
+ "pure-rand": "^6.0.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-cli": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz",
+ "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jest/core": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "chalk": "^4.0.0",
+ "create-jest": "^29.7.0",
+ "exit": "^0.1.2",
+ "import-local": "^3.0.2",
+ "jest-config": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "yargs": "^17.3.1"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-config": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz",
+ "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/core": "^7.11.6",
+ "@jest/test-sequencer": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "babel-jest": "^29.7.0",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "deepmerge": "^4.2.2",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "jest-circus": "^29.7.0",
+ "jest-environment-node": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.7.0",
+ "jest-runner": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "parse-json": "^5.2.0",
+ "pretty-format": "^29.7.0",
+ "slash": "^3.0.0",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "@types/node": "*",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-diff": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz",
+ "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "diff-sequences": "^29.6.3",
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-docblock": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz",
+ "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "detect-newline": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-each": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz",
+ "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "chalk": "^4.0.0",
+ "jest-get-type": "^29.6.3",
+ "jest-util": "^29.7.0",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-environment-node": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz",
+ "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jest/environment": "^29.7.0",
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "jest-mock": "^29.7.0",
+ "jest-util": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-get-type": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz",
+ "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-haste-map": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz",
+ "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/graceful-fs": "^4.1.3",
+ "@types/node": "*",
+ "anymatch": "^3.0.3",
+ "fb-watchman": "^2.0.0",
+ "graceful-fs": "^4.2.9",
+ "jest-regex-util": "^29.6.3",
+ "jest-util": "^29.7.0",
+ "jest-worker": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "walker": "^1.0.8"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "^2.3.2"
+ }
+ },
+ "node_modules/jest-leak-detector": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz",
+ "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-matcher-utils": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz",
+ "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "jest-diff": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-message-util": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz",
+ "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.12.13",
+ "@jest/types": "^29.6.3",
+ "@types/stack-utils": "^2.0.0",
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.9",
+ "micromatch": "^4.0.4",
+ "pretty-format": "^29.7.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-mock": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz",
+ "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "jest-util": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-pnp-resolver": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz",
+ "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ },
+ "peerDependencies": {
+ "jest-resolve": "*"
+ },
+ "peerDependenciesMeta": {
+ "jest-resolve": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-regex-util": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz",
+ "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-resolve": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz",
+ "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "jest-pnp-resolver": "^1.2.2",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "resolve": "^1.20.0",
+ "resolve.exports": "^2.0.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-resolve-dependencies": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz",
+ "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "jest-regex-util": "^29.6.3",
+ "jest-snapshot": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-runner": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz",
+ "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jest/console": "^29.7.0",
+ "@jest/environment": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "emittery": "^0.13.1",
+ "graceful-fs": "^4.2.9",
+ "jest-docblock": "^29.7.0",
+ "jest-environment-node": "^29.7.0",
+ "jest-haste-map": "^29.7.0",
+ "jest-leak-detector": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-resolve": "^29.7.0",
+ "jest-runtime": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-watcher": "^29.7.0",
+ "jest-worker": "^29.7.0",
+ "p-limit": "^3.1.0",
+ "source-map-support": "0.5.13"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-runtime": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz",
+ "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jest/environment": "^29.7.0",
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/globals": "^29.7.0",
+ "@jest/source-map": "^29.6.3",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "cjs-module-lexer": "^1.0.0",
+ "collect-v8-coverage": "^1.0.0",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-mock": "^29.7.0",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.7.0",
+ "jest-snapshot": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "slash": "^3.0.0",
+ "strip-bom": "^4.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-snapshot": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz",
+ "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/core": "^7.11.6",
+ "@babel/generator": "^7.7.2",
+ "@babel/plugin-syntax-jsx": "^7.7.2",
+ "@babel/plugin-syntax-typescript": "^7.7.2",
+ "@babel/types": "^7.3.3",
+ "@jest/expect-utils": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "babel-preset-current-node-syntax": "^1.0.0",
+ "chalk": "^4.0.0",
+ "expect": "^29.7.0",
+ "graceful-fs": "^4.2.9",
+ "jest-diff": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "jest-matcher-utils": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "natural-compare": "^1.4.0",
+ "pretty-format": "^29.7.0",
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-snapshot/node_modules/semver": {
+ "version": "7.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
+ "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
+ "dev": true,
+ "license": "ISC",
+ "peer": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/jest-util": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
+ "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "graceful-fs": "^4.2.9",
+ "picomatch": "^2.2.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-validate": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz",
+ "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "camelcase": "^6.2.0",
+ "chalk": "^4.0.0",
+ "jest-get-type": "^29.6.3",
+ "leven": "^3.1.0",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-validate/node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/jest-watcher": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz",
+ "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jest/test-result": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "ansi-escapes": "^4.2.1",
+ "chalk": "^4.0.0",
+ "emittery": "^0.13.1",
+ "jest-util": "^29.7.0",
+ "string-length": "^4.0.1"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-worker": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz",
+ "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/node": "*",
+ "jest-util": "^29.7.0",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-worker/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/jose": {
+ "version": "4.15.9",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz",
+ "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-bigint": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
+ "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "bignumber.js": "^9.0.0"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsonwebtoken": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
+ "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "jws": "^3.2.2",
+ "lodash.includes": "^4.3.0",
+ "lodash.isboolean": "^3.0.3",
+ "lodash.isinteger": "^4.0.4",
+ "lodash.isnumber": "^3.0.3",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.isstring": "^4.0.1",
+ "lodash.once": "^4.0.0",
+ "ms": "^2.1.1",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/jsonwebtoken/node_modules/jwa": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
+ "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-equal-constant-time": "1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jsonwebtoken/node_modules/jws": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+ "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+ "license": "MIT",
+ "dependencies": {
+ "jwa": "^1.4.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jsonwebtoken/node_modules/semver": {
+ "version": "7.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
+ "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/jwa": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
+ "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "buffer-equal-constant-time": "1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jwks-rsa": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.2.0.tgz",
+ "integrity": "sha512-PwchfHcQK/5PSydeKCs1ylNym0w/SSv8a62DgHJ//7x2ZclCoinlsjAfDxAAbpoTPybOum/Jgy+vkvMmKz89Ww==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/express": "^4.17.20",
+ "@types/jsonwebtoken": "^9.0.4",
+ "debug": "^4.3.4",
+ "jose": "^4.15.4",
+ "limiter": "^1.1.5",
+ "lru-memoizer": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/jws": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
+ "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "jwa": "^2.0.0",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/kleur": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
+ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/leven": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
+ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/limiter": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz",
+ "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA=="
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lodash.camelcase": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
+ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/lodash.clonedeep": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+ "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.includes": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+ "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isboolean": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isinteger": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+ "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isnumber": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+ "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isstring": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lodash.once": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
+ "license": "MIT"
+ },
+ "node_modules/long": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.3.1.tgz",
+ "integrity": "sha512-ka87Jz3gcx/I7Hal94xaN2tZEOPoUOEVftkQqZx2EeQRN7LGdfLlI3FvZ+7WDplm+vK2Urx9ULrvSowtdCieng==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "peer": true,
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/lru-memoizer": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz",
+ "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==",
+ "license": "MIT",
+ "dependencies": {
+ "lodash.clonedeep": "^4.5.0",
+ "lru-cache": "6.0.0"
+ }
+ },
+ "node_modules/lru-memoizer/node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/lru-memoizer/node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "license": "ISC"
+ },
+ "node_modules/make-dir": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
+ "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/make-dir/node_modules/semver": {
+ "version": "7.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
+ "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
+ "dev": true,
+ "license": "ISC",
+ "peer": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/makeerror": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
+ "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "dependencies": {
+ "tmpl": "1.0.5"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
+ "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
+ "license": "MIT",
+ "optional": true,
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/node-forge": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
+ "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
+ "license": "(BSD-3-Clause OR GPL-2.0)",
+ "engines": {
+ "node": ">= 6.13.0"
+ }
+ },
+ "node_modules/node-int64": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
+ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.19",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
+ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "path-key": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "devOptional": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
+ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC",
+ "peer": true
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/pkg-dir": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "find-up": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/pkg-dir/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/prompts": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
+ "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "kleur": "^3.0.3",
+ "sisteransi": "^1.0.5"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/proto3-json-serializer": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz",
+ "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "protobufjs": "^7.2.5"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/protobufjs": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz",
+ "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==",
+ "hasInstallScript": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.2",
+ "@protobufjs/base64": "^1.1.2",
+ "@protobufjs/codegen": "^2.0.4",
+ "@protobufjs/eventemitter": "^1.1.0",
+ "@protobufjs/fetch": "^1.1.0",
+ "@protobufjs/float": "^1.0.2",
+ "@protobufjs/inquire": "^1.1.0",
+ "@protobufjs/path": "^1.1.2",
+ "@protobufjs/pool": "^1.1.0",
+ "@protobufjs/utf8": "^1.1.0",
+ "@types/node": ">=13.7.0",
+ "long": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/pure-rand": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
+ "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/qs": {
+ "version": "6.13.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
+ "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.0.6"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.10",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
+ "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "is-core-module": "^2.16.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-cwd": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
+ "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/resolve-cwd/node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/resolve.exports": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz",
+ "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/retry": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
+ "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/retry-request": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz",
+ "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@types/request": "^2.48.8",
+ "extend": "^3.0.2",
+ "teeny-request": "^9.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "peer": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/send": {
+ "version": "0.19.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
+ "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/send/node_modules/debug/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/send/node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/send/node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "1.16.2",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
+ "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "0.19.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true,
+ "license": "ISC",
+ "peer": true
+ },
+ "node_modules/sisteransi": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
+ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-support": {
+ "version": "0.5.13",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
+ "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "peer": true
+ },
+ "node_modules/stack-utils": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
+ "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "escape-string-regexp": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/stack-utils/node_modules/escape-string-regexp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/stream-events": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz",
+ "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "stubs": "^3.0.0"
+ }
+ },
+ "node_modules/stream-shift": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz",
+ "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/string-length": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
+ "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "char-regex": "^1.0.2",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-bom": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
+ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/strnum": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz",
+ "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/NaturalIntelligence"
+ }
+ ],
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/stubs": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz",
+ "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/teeny-request": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz",
+ "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "http-proxy-agent": "^5.0.0",
+ "https-proxy-agent": "^5.0.0",
+ "node-fetch": "^2.6.9",
+ "stream-events": "^1.0.5",
+ "uuid": "^9.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/teeny-request/node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/teeny-request/node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/teeny-request/node_modules/uuid": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
+ "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/test-exclude": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
+ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
+ "dev": true,
+ "license": "ISC",
+ "peer": true,
+ "dependencies": {
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^7.1.4",
+ "minimatch": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tmpl": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
+ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "peer": true
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/ts-deepmerge": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/ts-deepmerge/-/ts-deepmerge-2.0.7.tgz",
+ "integrity": "sha512-3phiGcxPSSR47RBubQxPoZ+pqXsEsozLo4G4AlSrsMKTFg9TA3l+3he5BqpUi9wiuDbaHWXH/amlzQ49uEdXtg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-detect": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "license": "MIT",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.20.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
+ "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
+ "license": "MIT"
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/uuid": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
+ "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/v8-to-istanbul": {
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
+ "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
+ "dev": true,
+ "license": "ISC",
+ "peer": true,
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.12",
+ "@types/istanbul-lib-coverage": "^2.0.1",
+ "convert-source-map": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10.12.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/walker": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
+ "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "makeerror": "1.0.12"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "license": "BSD-2-Clause",
+ "optional": true
+ },
+ "node_modules/websocket-driver": {
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
+ "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "http-parser-js": ">=0.5.1",
+ "safe-buffer": ">=5.1.0",
+ "websocket-extensions": ">=0.1.1"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/websocket-extensions": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
+ "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "devOptional": true,
+ "license": "ISC"
+ },
+ "node_modules/write-file-atomic": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz",
+ "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==",
+ "dev": true,
+ "license": "ISC",
+ "peer": true,
+ "dependencies": {
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^3.0.7"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "devOptional": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC",
+ "peer": true
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "devOptional": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "devOptional": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/functions/package.json b/functions/package.json
new file mode 100644
index 00000000..ded79f2b
--- /dev/null
+++ b/functions/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "functions",
+ "description": "Cloud Functions for Firebase",
+ "scripts": {
+ "lint": "exit 0",
+ "build": "echo No build required",
+ "serve": "firebase emulators:start --only functions",
+ "shell": "firebase functions:shell",
+ "start": "npm run shell",
+ "deploy": "firebase deploy --only functions",
+ "logs": "firebase functions:log"
+},
+ "engines": {
+ "node": "22"
+ },
+ "main": "index.js",
+ "dependencies": {
+ "firebase-admin": "^12.6.0",
+ "firebase-functions": "^6.0.1"
+ },
+ "devDependencies": {
+ "eslint": "^8.15.0",
+ "eslint-config-google": "^0.14.0",
+ "firebase-functions-test": "^3.1.0"
+ },
+ "private": true
+}
diff --git a/team_a/.gitignore b/team_a/.gitignore
deleted file mode 100644
index 25fd9094..00000000
--- a/team_a/.gitignore
+++ /dev/null
@@ -1,41 +0,0 @@
-# See https://www.dartlang.org/guides/libraries/private-files
-
-# Files and directories created by pub
-.dart_tool/
-.packages
-build/
-# If you're building an application, you may want to check-in your pubspec.lock
-pubspec.lock
-
-# Directory created by dartdoc
-# If you don't generate documentation locally you can remove this line.
-doc/api/
-
-# dotenv environment variables file
-.env*
-
-# Avoid committing generated Javascript files:
-*.dart.js
-*.info.json # Produced by the --dump-info flag.
-*.js # When generated by dart2js. Don't specify *.js if your
- # project includes source files written in JavaScript.
-*.js_
-*.js.deps
-*.js.map
-
-.flutter-plugins
-.flutter-plugins-dependencies
-.vs/*
-.vscode/
-.vs*
-*/ios/
-linux/
-macos/
-web/
-windows/
-Flutter/
-teama/linux/*
-teama/macos/*
-teama/windows/*
-teama/assets/api-keys.json
-teama/android/
diff --git a/team_a/LICENSE b/team_a/LICENSE
deleted file mode 100644
index 0e259d42..00000000
--- a/team_a/LICENSE
+++ /dev/null
@@ -1,121 +0,0 @@
-Creative Commons Legal Code
-
-CC0 1.0 Universal
-
- CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
- LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
- ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
- INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
- REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
- PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
- THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
- HEREUNDER.
-
-Statement of Purpose
-
-The laws of most jurisdictions throughout the world automatically confer
-exclusive Copyright and Related Rights (defined below) upon the creator
-and subsequent owner(s) (each and all, an "owner") of an original work of
-authorship and/or a database (each, a "Work").
-
-Certain owners wish to permanently relinquish those rights to a Work for
-the purpose of contributing to a commons of creative, cultural and
-scientific works ("Commons") that the public can reliably and without fear
-of later claims of infringement build upon, modify, incorporate in other
-works, reuse and redistribute as freely as possible in any form whatsoever
-and for any purposes, including without limitation commercial purposes.
-These owners may contribute to the Commons to promote the ideal of a free
-culture and the further production of creative, cultural and scientific
-works, or to gain reputation or greater distribution for their Work in
-part through the use and efforts of others.
-
-For these and/or other purposes and motivations, and without any
-expectation of additional consideration or compensation, the person
-associating CC0 with a Work (the "Affirmer"), to the extent that he or she
-is an owner of Copyright and Related Rights in the Work, voluntarily
-elects to apply CC0 to the Work and publicly distribute the Work under its
-terms, with knowledge of his or her Copyright and Related Rights in the
-Work and the meaning and intended legal effect of CC0 on those rights.
-
-1. Copyright and Related Rights. A Work made available under CC0 may be
-protected by copyright and related or neighboring rights ("Copyright and
-Related Rights"). Copyright and Related Rights include, but are not
-limited to, the following:
-
- i. the right to reproduce, adapt, distribute, perform, display,
- communicate, and translate a Work;
- ii. moral rights retained by the original author(s) and/or performer(s);
-iii. publicity and privacy rights pertaining to a person's image or
- likeness depicted in a Work;
- iv. rights protecting against unfair competition in regards to a Work,
- subject to the limitations in paragraph 4(a), below;
- v. rights protecting the extraction, dissemination, use and reuse of data
- in a Work;
- vi. database rights (such as those arising under Directive 96/9/EC of the
- European Parliament and of the Council of 11 March 1996 on the legal
- protection of databases, and under any national implementation
- thereof, including any amended or successor version of such
- directive); and
-vii. other similar, equivalent or corresponding rights throughout the
- world based on applicable law or treaty, and any national
- implementations thereof.
-
-2. Waiver. To the greatest extent permitted by, but not in contravention
-of, applicable law, Affirmer hereby overtly, fully, permanently,
-irrevocably and unconditionally waives, abandons, and surrenders all of
-Affirmer's Copyright and Related Rights and associated claims and causes
-of action, whether now known or unknown (including existing as well as
-future claims and causes of action), in the Work (i) in all territories
-worldwide, (ii) for the maximum duration provided by applicable law or
-treaty (including future time extensions), (iii) in any current or future
-medium and for any number of copies, and (iv) for any purpose whatsoever,
-including without limitation commercial, advertising or promotional
-purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
-member of the public at large and to the detriment of Affirmer's heirs and
-successors, fully intending that such Waiver shall not be subject to
-revocation, rescission, cancellation, termination, or any other legal or
-equitable action to disrupt the quiet enjoyment of the Work by the public
-as contemplated by Affirmer's express Statement of Purpose.
-
-3. Public License Fallback. Should any part of the Waiver for any reason
-be judged legally invalid or ineffective under applicable law, then the
-Waiver shall be preserved to the maximum extent permitted taking into
-account Affirmer's express Statement of Purpose. In addition, to the
-extent the Waiver is so judged Affirmer hereby grants to each affected
-person a royalty-free, non transferable, non sublicensable, non exclusive,
-irrevocable and unconditional license to exercise Affirmer's Copyright and
-Related Rights in the Work (i) in all territories worldwide, (ii) for the
-maximum duration provided by applicable law or treaty (including future
-time extensions), (iii) in any current or future medium and for any number
-of copies, and (iv) for any purpose whatsoever, including without
-limitation commercial, advertising or promotional purposes (the
-"License"). The License shall be deemed effective as of the date CC0 was
-applied by Affirmer to the Work. Should any part of the License for any
-reason be judged legally invalid or ineffective under applicable law, such
-partial invalidity or ineffectiveness shall not invalidate the remainder
-of the License, and in such case Affirmer hereby affirms that he or she
-will not (i) exercise any of his or her remaining Copyright and Related
-Rights in the Work or (ii) assert any associated claims and causes of
-action with respect to the Work, in either case contrary to Affirmer's
-express Statement of Purpose.
-
-4. Limitations and Disclaimers.
-
- a. No trademark or patent rights held by Affirmer are waived, abandoned,
- surrendered, licensed or otherwise affected by this document.
- b. Affirmer offers the Work as-is and makes no representations or
- warranties of any kind concerning the Work, express, implied,
- statutory or otherwise, including without limitation warranties of
- title, merchantability, fitness for a particular purpose, non
- infringement, or the absence of latent or other defects, accuracy, or
- the present or absence of errors, whether or not discoverable, all to
- the greatest extent permissible under applicable law.
- c. Affirmer disclaims responsibility for clearing rights of other persons
- that may apply to the Work or any use thereof, including without
- limitation any person's Copyright and Related Rights in the Work.
- Further, Affirmer disclaims responsibility for obtaining any necessary
- consents, permissions or other rights required for any use of the
- Work.
- d. Affirmer understands and acknowledges that Creative Commons is not a
- party to this document and has no duty or obligation with respect to
- this CC0 or use of the Work.
diff --git a/team_a/MoodlePlugin/.DS_Store b/team_a/MoodlePlugin/.DS_Store
deleted file mode 100644
index e9963cd3..00000000
Binary files a/team_a/MoodlePlugin/.DS_Store and /dev/null differ
diff --git a/team_a/MoodlePlugin/learninglens/.DS_Store b/team_a/MoodlePlugin/learninglens/.DS_Store
deleted file mode 100644
index b69e9e2d..00000000
Binary files a/team_a/MoodlePlugin/learninglens/.DS_Store and /dev/null differ
diff --git a/team_a/MoodlePlugin/learninglens/classes/.DS_Store b/team_a/MoodlePlugin/learninglens/classes/.DS_Store
deleted file mode 100644
index 6fc52684..00000000
Binary files a/team_a/MoodlePlugin/learninglens/classes/.DS_Store and /dev/null differ
diff --git a/team_a/MoodlePlugin/learninglens/classes/external/add_essay_override.php b/team_a/MoodlePlugin/learninglens/classes/external/add_essay_override.php
deleted file mode 100644
index 87f6cc25..00000000
--- a/team_a/MoodlePlugin/learninglens/classes/external/add_essay_override.php
+++ /dev/null
@@ -1,80 +0,0 @@
-libdir . '/externallib.php');
-
-use external_function_parameters;
-use external_single_structure;
-use external_value;
-use external_api;
-
-class add_essay_override extends external_api {
-
- /**
- * Define parameters for the external function
- */
- public static function execute_parameters() {
- return new external_function_parameters([
- 'assignid' => new external_value(PARAM_INT, 'ID of the essay'),
- 'userid' => new external_value(PARAM_INT, 'User ID (null if group override)', VALUE_DEFAULT, null),
- 'groupid' => new external_value(PARAM_INT, 'Group ID (null if user override)', VALUE_DEFAULT, null),
- 'allowsubmissionsfromdate' => new external_value(PARAM_INT, 'Time when essay opens (null for default)', VALUE_DEFAULT, null),
- 'duedate' => new external_value(PARAM_INT, 'Time when essay closes (null for default)', VALUE_DEFAULT, null),
- 'cutoffdate' => new external_value(PARAM_INT, 'Time when essay submission is no longer allowed (null for default)', VALUE_DEFAULT, null),
- 'timelimit' => new external_value(PARAM_INT, 'Essay time limit in seconds (null for default)', VALUE_DEFAULT, null),
- 'sortorder' => new external_value(PARAM_INT, 'Essay sort order (null for default)', VALUE_DEFAULT, null),
- ]);
- }
-
- /**
- * Inserts a new quiz override record
- */
- public static function execute($assignid, $userid = null, $groupid = null, $allowsubmissionsfromdate = null, $duedate = null, $cutoffdate = null, $timelimit = null, $sortorder = null) {
- global $DB;
-
- // Validate parameters
- $params = self::validate_parameters(self::execute_parameters(), [
- 'assignid' => $assignid,
- 'userid' => $userid,
- 'groupid' => $groupid,
- 'allowsubmissionsfromdate' => $allowsubmissionsfromdate,
- 'duedate' => $duedate,
- 'cutoffdate' => $cutoffdate,
- 'timelimit' => $timelimit,
- 'sortorder' => $sortorder,
- ]);
-
- // Ensure either a user or group ID is provided
- if (empty($params['userid']) && empty($params['groupid'])) {
- throw new \moodle_exception('invalidoverride', 'local_learninglens', '', 'Either a userid or groupid must be provided.');
- }
-
- // Prepare data for insertion
- $record = new \stdClass();
- $record->assignid = $params['assignid'];
- $record->userid = $params['userid'];
- $record->groupid = $params['groupid'];
- $record->allowsubmissionsfromdate = $params['allowsubmissionsfromdate'];
- $record->duedate = $params['duedate'];
- $record->cutoffdate = $params['cutoffdate'];
- $record->timelimit = $params['timelimit'];
- $record->sortorder = $params['sortorder'];
-
- // Insert into the database
- $overrideid = $DB->insert_record('assign_overrides', $record);
-
- // Return the inserted override ID
- return ['overrideid' => $overrideid];
- }
-
- /**
- * Defines the return structure
- */
- public static function execute_returns() {
- return new external_single_structure([
- 'overrideid' => new external_value(PARAM_INT, 'ID of the created quiz override'),
- ]);
- }
-}
diff --git a/team_a/MoodlePlugin/learninglens/classes/external/add_quiz_override.php b/team_a/MoodlePlugin/learninglens/classes/external/add_quiz_override.php
deleted file mode 100644
index 0d9a9116..00000000
--- a/team_a/MoodlePlugin/learninglens/classes/external/add_quiz_override.php
+++ /dev/null
@@ -1,80 +0,0 @@
-libdir . '/externallib.php');
-
-use external_function_parameters;
-use external_single_structure;
-use external_value;
-use external_api;
-
-class add_quiz_override extends external_api {
-
- /**
- * Define parameters for the external function
- */
- public static function execute_parameters() {
- return new external_function_parameters([
- 'quizid' => new external_value(PARAM_INT, 'ID of the quiz'),
- 'userid' => new external_value(PARAM_INT, 'User ID (null if group override)', VALUE_DEFAULT, null),
- 'groupid' => new external_value(PARAM_INT, 'Group ID (null if user override)', VALUE_DEFAULT, null),
- 'timeopen' => new external_value(PARAM_INT, 'Time when quiz opens (null for default)', VALUE_DEFAULT, null),
- 'timeclose' => new external_value(PARAM_INT, 'Time when quiz closes (null for default)', VALUE_DEFAULT, null),
- 'timelimit' => new external_value(PARAM_INT, 'Quiz time limit in seconds (null for default)', VALUE_DEFAULT, null),
- 'attempts' => new external_value(PARAM_INT, 'Allowed attempts (null for default)', VALUE_DEFAULT, null),
- 'password' => new external_value(PARAM_TEXT, 'Quiz password (null for default)', VALUE_DEFAULT, null),
- ]);
- }
-
- /**
- * Inserts a new quiz override record
- */
- public static function execute($quizid, $userid = null, $groupid = null, $timeopen = null, $timeclose = null, $timelimit = null, $attempts = null, $password = null) {
- global $DB;
-
- // Validate parameters
- $params = self::validate_parameters(self::execute_parameters(), [
- 'quizid' => $quizid,
- 'userid' => $userid,
- 'groupid' => $groupid,
- 'timeopen' => $timeopen,
- 'timeclose' => $timeclose,
- 'timelimit' => $timelimit,
- 'attempts' => $attempts,
- 'password' => $password,
- ]);
-
- // Ensure either a user or group ID is provided
- if (empty($params['userid']) && empty($params['groupid'])) {
- throw new \moodle_exception('invalidoverride', 'local_learninglens', '', 'Either a userid or groupid must be provided.');
- }
-
- // Prepare data for insertion
- $record = new \stdClass();
- $record->quiz = $params['quizid'];
- $record->userid = $params['userid'];
- $record->groupid = $params['groupid'];
- $record->timeopen = $params['timeopen'];
- $record->timeclose = $params['timeclose'];
- $record->timelimit = $params['timelimit'];
- $record->attempts = $params['attempts'];
- $record->password = $params['password'];
-
- // Insert into the database
- $overrideid = $DB->insert_record('quiz_overrides', $record);
-
- // Return the inserted override ID
- return ['overrideid' => $overrideid];
- }
-
- /**
- * Defines the return structure
- */
- public static function execute_returns() {
- return new external_single_structure([
- 'overrideid' => new external_value(PARAM_INT, 'ID of the created quiz override'),
- ]);
- }
-}
diff --git a/team_a/MoodlePlugin/learninglens/classes/external/add_type_randoms_to_quiz.php b/team_a/MoodlePlugin/learninglens/classes/external/add_type_randoms_to_quiz.php
deleted file mode 100644
index 04ff2598..00000000
--- a/team_a/MoodlePlugin/learninglens/classes/external/add_type_randoms_to_quiz.php
+++ /dev/null
@@ -1,65 +0,0 @@
-libdir . '/externallib.php');
-require_once($CFG->dirroot . '/mod/quiz/locallib.php');
-
-use context_course;
-use core_external\external_api;
-use core_external\external_function_parameters;
-use core_external\external_multiple_structure;
-use core_external\external_single_structure;
-use core_external\external_value;
-use core_question\local\bank\question_edit_contexts;
-use qformat_xml;
-
-class add_type_randoms_to_quiz extends external_api {
-
- public static function execute_parameters() {
- return new external_function_parameters([
- 'quizid' => new external_value(PARAM_INT, 'The quiz ID'),
- 'categoryid' => new external_value(PARAM_INT, 'Question Category ID'),
- 'numquestions' => new external_value(PARAM_INT, 'Number of Questions to add')
- ]);
- }
-
- public static function execute_returns() {
- return new external_value(PARAM_BOOL, 'Returns TRUE if succesful');
-
- }
-
- public static function execute(int $quizid, int $categoryid, int $numquestions): bool {
- global $DB;
-
- $params = self::validate_parameters(self::execute_parameters(), [
- 'quizid' => $quizid,
- 'categoryid' => $categoryid,
- 'numquestions' => $numquestions,
- ]);
-
- try {
- // Fetch the quiz object.
- $quiz = $DB->get_record('quiz', ['id' => $quizid], '*', MUST_EXIST);
-
- // Fetch the module context for the quiz.
- $cm = get_coursemodule_from_instance('quiz', $quizid, $quiz->course, false, MUST_EXIST);
- $context = \context_module::instance($cm->id);
-
- quiz_add_random_questions($quiz, $addonpage = 0, $categoryid, $numquestions, false);
-
- // Recalculate the sumgrades automatically using Moodle's built-in function
- quiz_update_sumgrades($quiz);
- quiz_grade_item_update($quiz);
-
- return true; // Return true on successful addition.
-
- } catch (Exception $e) {
- // Log the error message or handle the error as needed.
- debugging('Error adding random questions to quiz: ' . $e->getMessage());
- return false; // Return false if an error occurs.
- }
- }
-}
diff --git a/team_a/MoodlePlugin/learninglens/classes/external/create_assignment.php b/team_a/MoodlePlugin/learninglens/classes/external/create_assignment.php
deleted file mode 100644
index f708f6ac..00000000
--- a/team_a/MoodlePlugin/learninglens/classes/external/create_assignment.php
+++ /dev/null
@@ -1,245 +0,0 @@
-libdir . '/externallib.php');
-require_once($CFG->dirroot . '/course/modlib.php');
-require_once($CFG->dirroot . '/mod/assign/lib.php');
-require_once($CFG->dirroot.'/grade/grading/lib.php');
-require_once($CFG->dirroot.'/grade/grading/form/rubric/lib.php');
-require_once($CFG->dirroot.'/course/lib.php'); // For course functions
-
-
-use external_function_parameters;
-use external_single_structure;
-use external_value;
-use context_course;
-use context_module;
-use coding_exception;
-use external_api;
-
-class create_assignment extends external_api {
-
- // Define the input parameters for the web service
- public static function execute_parameters() {
- return new external_function_parameters(
- array(
- 'courseid' => new external_value(PARAM_INT, 'Course ID'),
- 'sectionid' => new external_value(PARAM_INT, 'Section ID'),
- 'assignmentName' => new external_value(PARAM_TEXT, 'Assignment name'),
- 'startdate' => new external_value(PARAM_TEXT, 'Start date (timestamp)'),
- 'enddate' => new external_value(PARAM_TEXT, 'End date (timestamp)'),
- 'rubricJson' => new external_value(PARAM_RAW, 'Rubric JSON', VALUE_DEFAULT, ''),
- 'description' => new external_value(PARAM_RAW, 'Assignment description', VALUE_DEFAULT, '')
- )
- );
- }
-
- // The actual function that performs the task
- public static function execute($courseid, $sectionid, $assignmentName, $startdate, $enddate, $rubricJson = '', $description = '') {
- global $USER, $DB;
-
- // Validate the parameters
- $params = self::validate_parameters(
- self::execute_parameters(),
- array(
- 'courseid' => $courseid,
- 'sectionid' => $sectionid,
- 'assignmentName' => $assignmentName,
- 'startdate' => $startdate,
- 'enddate' => $enddate,
- 'rubricJson' => $rubricJson,
- 'description' => $description
- )
- );
-
- // Convert date strings to timestamps
- $startdate_timestamp = strtotime($startdate);
- $enddate_timestamp = strtotime($enddate);
-
- // Validate the conversion
- if ($startdate_timestamp === false) {
- throw new invalid_parameter_exception('Invalid start date format.');
- }
- if ($enddate_timestamp === false) {
- throw new invalid_parameter_exception('Invalid end date format.');
- }
-
- // Capability check
- $context = context_course::instance($courseid);
- require_capability('mod/assign:addinstance', $context);
-
- // Prepare the course module data
- $moduleid = $DB->get_field('modules', 'id', ['name' => 'assign']);
- $cm = new \stdClass();
- $cm->course = $courseid;
- $cm->module = $moduleid;
- $cm->section = $sectionid;
- $cm->visible = 1;
-
- // Add the course module entry
- $cmid = add_course_module($cm);
-
- // Prepare the assignment data
- $assignment_data = new \stdClass();
- $assignment_data->course = $courseid;
- $assignment_data->name = $assignmentName;
- $assignment_data->intro = $description;
- $assignment_data->introformat = FORMAT_HTML;
- $assignment_data->duedate = $enddate_timestamp;
- $assignment_data->allowsubmissionsfromdate = $startdate_timestamp;
- $assignment_data->grade = 100;
- $assignment_data->coursemodule = $cmid;
-
- // Required fields that must be set
- $assignment_data->submissiondrafts = 0; // Allow submission drafts: 0 = No, 1 = Yes
- $assignment_data->requiresubmissionstatement = 0; // Require submission statement
- $assignment_data->sendnotifications = 1; // Send notifications to graders
- $assignment_data->sendlatenotifications = 1; // Send notifications about late submissions
- $assignment_data->sendstudentnotifications = 1; // Notify students
- $assignment_data->teamsubmission = 0; // Team submissions
- $assignment_data->requireallteammemberssubmit = 0; // Require all team members to submit
- $assignment_data->blindmarking = 0; // Blind marking
- $assignment_data->attemptreopenmethod = 'none'; // Attempt reopen method: 'none', 'manual', 'untilpass'
- $assignment_data->maxattempts = -1; // Max attempts (-1 for unlimited)
- $assignment_data->markingworkflow = 0; // Marking workflow
- $assignment_data->markingallocation = 0; // Marking allocation
- $assignment_data->cutoffdate = $enddate_timestamp; // Cut-off date
- $assignment_data->gradingduedate = 0; // Grading due date
- $assignment_data->grade = 100; // Maximum grade
- $assignment_data->completionsubmit = 0; // Completion tracking
- $assignment_data->alwaysshowdescription = 1; // Always show description
-
- // Initialize submission plugin settings
- $assignment_data->assignsubmission_onlinetext_enabled = 1; // Enable online text submissions
- $assignment_data->assignsubmission_file_enabled = 0; // Disable file submissions
-
- // Create the assignment instance
- $assignment_instance = assign_add_instance($assignment_data);
-
- // Update the course module with the correct instance ID
- $DB->set_field('course_modules', 'instance', $assignment_instance, ['id' => $cmid]);
-
- // Add the course module to the section
- course_add_cm_to_section($courseid, $cmid, $sectionid);
-
- // Set the assignment to use advanced grading (rubric) if rubricJson is provided
- if (!empty($rubricJson)) {
- // Create the context_module object
- $module_context = context_module::instance($cmid);
-
- require_capability('moodle/grade:managegradingforms', $module_context);
-
- // Initialize the grading manager
- $grading_manager = get_grading_manager($module_context);
- $grading_manager->set_area('submissions');
- $grading_manager->set_component('mod_assign');
- $grading_manager->set_active_method('rubric');
-
- // Get the controller for the 'rubric' grading method
- $rubric_controller = $grading_manager->get_controller('rubric');
-
- // Create the rubric definition using the custom function
- $rubric_definition = self::create_rubric_definition_from_json($rubricJson);
-
- $rubric_controller->update_definition($rubric_definition);
- $rubricid = $rubric_controller->get_definition()->id;
- }
-
- return array('assignmentid' => $assignment_instance, 'assignmentname' => $assignmentName, 'rubricid' => $rubricid);
- }
-
- public static function execute_returns() {
- return new external_single_structure(
- array(
- 'assignmentid' => new external_value(PARAM_INT, 'Assignment ID'),
- 'assignmentname' => new external_value(PARAM_TEXT, 'Assignment name'),
- 'rubricid' => new external_value(PARAM_TEXT, 'Rubric ID')
- )
- );
- }
-
- public static function create_rubric_definition_from_json($rubricJson) {
- // Decode the JSON input into an associative array
- $rubric_data = json_decode($rubricJson, true);
-
- // Check if the JSON is valid
- if (json_last_error() !== JSON_ERROR_NONE) {
- throw new coding_exception('Invalid JSON format.');
- }
-
- // Validate that the required fields (criteria and levels) exist in the JSON
- if (!isset($rubric_data['criteria']) || !is_array($rubric_data['criteria'])) {
- throw new coding_exception('Invalid rubric format: missing criteria.');
- }
-
- // Initialize the rubric definition object (stdClass)
- $rubric_definition = new \stdClass();
- $rubric_definition->status = 20; // 20 represents 'active'
- $rubric_definition->description = ''; // Optional: Add rubric description if needed
- $rubric_definition->name = 'Rubric Name'; // Optional: Set the rubric name
-
- // Initialize the 'rubric' property with 'criteria' and 'options'
- $rubric_definition->rubric = array(
- 'criteria' => array(),
- 'options' => array(
- 'sortlevelsasc' => 1,
- 'allowscoreoverrides' => 0,
- 'showdescriptionteacher' => 1,
- 'showdescriptionstudent' => 0,
- // Add other options as required
- ),
- );
-
- // Variables to keep track of criterion and level IDs
- $criterion_id = 0;
- $level_id = 0;
-
- // Loop through each criterion in the JSON data
- foreach ($rubric_data['criteria'] as $criterion_data) {
- // Each criterion will be an associative array with 'levels' as an array
- $criterion_array = array();
- $criterion_array['id'] = $criterion_id;
- $criterion_array['description'] = $criterion_data['description'];
- $criterion_array['sortorder'] = $criterion_id; // Sort order starts at 0
- $criterion_array['levels'] = array();
-
- // Validate that the levels field exists
- if (!isset($criterion_data['levels']) || !is_array($criterion_data['levels'])) {
- throw new coding_exception('Invalid rubric format: missing levels for criterion.');
- }
-
- // Level counter for keys
- $level_key = 0;
-
- // Loop through each level in the criterion
- foreach ($criterion_data['levels'] as $level_data) {
- // Each level is an associative array with a definition (name) and score (points)
- $level_array = array();
- $level_array['id'] = $level_id; // ID is integer
- $level_array['definition'] = $level_data['definition'];
- $level_array['score'] = $level_data['score'];
-
- // Assign the level array to the criterion's levels array with 'NEWID' keys
- // This is required for the update_definition method to see them as new levels
- $criterion_array['levels']['NEWID' . $level_key] = $level_array;
-
- $level_id++;
- $level_key++;
- }
-
- // Assign the criterion array to the rubric_definition's rubric['criteria'] array with 'NEWID' keys
- $rubric_definition->rubric['criteria']['NEWID' . $criterion_id] = $criterion_array;
- $criterion_id++;
- }
-
- // Add the description_editor property
- $rubric_definition->description_editor = array(
- 'text' => $rubric_definition->description,
- 'format' => FORMAT_HTML,
- );
-
- return $rubric_definition;
- }
-}
diff --git a/team_a/MoodlePlugin/learninglens/classes/external/create_lesson.php b/team_a/MoodlePlugin/learninglens/classes/external/create_lesson.php
deleted file mode 100644
index 1f4628c4..00000000
--- a/team_a/MoodlePlugin/learninglens/classes/external/create_lesson.php
+++ /dev/null
@@ -1,245 +0,0 @@
-libdir . '/externallib.php');
-require_once($CFG->dirroot . '/mod/lesson/lib.php');
-require_once($CFG->dirroot . '/course/lib.php'); // For rebuild_course_cache
-
-use external_function_parameters;
-use external_single_structure;
-use external_value;
-use context_course;
-use moodle_exception;
-use external_api;
-use stdClass;
-
-/**
- * Create a lesson and link it to the course module.
- * Allows for advanced configuration (password, showdescription, completion, etc.).
- */
-class create_lesson extends external_api {
-
- public static function execute_parameters(): external_function_parameters {
- return new external_function_parameters([
- 'courseid' => new external_value(PARAM_INT, 'Course ID in which to create the lesson'),
- 'name' => new external_value(PARAM_TEXT, 'Lesson name/title'),
-
- 'intro' => new external_value(PARAM_RAW, 'Lesson description/intro', VALUE_OPTIONAL, ''),
- 'introformat' => new external_value(PARAM_INT, 'Intro format (1=HTML, 0=MOODLE, etc.)', VALUE_OPTIONAL, FORMAT_HTML),
-
- // Visibility of intro on course page
- 'showdescription' => new external_value(
- PARAM_INT,
- 'Display description on course page? (1=yes,0=no)',
- VALUE_OPTIONAL,
- 1 // default to showing description
- ),
-
- // Lesson availability fields
- 'available' => new external_value(
- PARAM_INT,
- 'Timestamp when the lesson opens (defaults to now if 0)',
- VALUE_OPTIONAL,
- 0
- ),
- 'deadline' => new external_value(
- PARAM_INT,
- 'Timestamp for lesson deadline (0=no deadline)',
- VALUE_OPTIONAL,
- 0
- ),
- 'timelimit' => new external_value(
- PARAM_INT,
- 'Time limit (seconds). 0 means no limit',
- VALUE_OPTIONAL,
- 0
- ),
-
- // Attempts/retakes
- 'retake' => new external_value(
- PARAM_INT,
- 'Allow retakes? (1=yes,0=no)',
- VALUE_OPTIONAL,
- 1
- ),
- 'maxattempts' => new external_value(
- PARAM_INT,
- 'Maximum number of attempts allowed',
- VALUE_OPTIONAL,
- 3
- ),
-
- // Lesson password
- 'usepassword' => new external_value(
- PARAM_INT,
- '1 if a password is required, 0 otherwise',
- VALUE_OPTIONAL,
- 0
- ),
- 'password' => new external_value(
- PARAM_RAW,
- 'Lesson password if usepassword=1',
- VALUE_OPTIONAL,
- ''
- ),
-
- // Activity completion in course_modules
- 'completion' => new external_value(
- PARAM_INT,
- 'Enable completion tracking? (1=yes,0=no)',
- VALUE_OPTIONAL,
- 1
- ),
- ]);
- }
-
- public static function execute(
- $courseid,
- $name,
- $intro = '',
- $introformat = FORMAT_HTML,
- $showdescription = 1,
-
- $available = 0,
- $deadline = 0,
- $timelimit = 0,
-
- $retake = 1,
- $maxattempts = 3,
-
- $usepassword = 0,
- $password = '',
-
- $completion = 1
- ) {
- global $DB;
-
- // 1. Validate parameters
- $params = self::validate_parameters(
- self::execute_parameters(),
- [
- 'courseid' => $courseid,
- 'name' => $name,
- 'intro' => $intro,
- 'introformat' => $introformat,
- 'showdescription' => $showdescription,
- 'available' => $available,
- 'deadline' => $deadline,
- 'timelimit' => $timelimit,
- 'retake' => $retake,
- 'maxattempts' => $maxattempts,
- 'usepassword' => $usepassword,
- 'password' => $password,
- 'completion' => $completion
- ]
- );
-
- // 2. Check if course exists
- if (!$DB->record_exists('course', ['id' => $params['courseid']])) {
- throw new moodle_exception('invalidcourseid', 'error', '', $params['courseid']);
- }
-
- // 3. Validate context/capability
- $context = context_course::instance($params['courseid']);
- self::validate_context($context);
- require_capability('mod/lesson:addinstance', $context);
-
- // 4. Create lesson record
- $lesson = new stdClass();
- $lesson->course = $params['courseid'];
- $lesson->name = $params['name'];
- $lesson->intro = $params['intro'];
- $lesson->introformat = $params['introformat'];
-
- // If 'available' is 0, we can default to now => time()
- $lesson->available = ($params['available'] == 0) ? time() : $params['available'];
-
- $lesson->deadline = $params['deadline'];
- $lesson->timelimit = $params['timelimit'];
- $lesson->retake = $params['retake'];
- $lesson->maxattempts = $params['maxattempts'];
-
- // Lesson password usage
- $lesson->usepassword = $params['usepassword']; // field typically in lesson table
- $lesson->password = $params['password'];
-
- $lesson->timecreated = time();
- $lesson->timemodified = time();
-
- // If your version of Moodle requires conditions => store empty JSON
- $lesson->conditions = '[]';
-
- // Insert into mdl_lesson
- $lessonid = $DB->insert_record('lesson', $lesson);
- if (!$lessonid) {
- throw new moodle_exception('errorcreatinglesson', 'local_learninglens');
- }
-
- // 5. Link lesson to the course modules
- // 5a. Get module ID for 'lesson'
- $module = $DB->get_record('modules', ['name' => 'lesson'], 'id');
- if (!$module) {
- throw new moodle_exception('modulenotfound', 'error');
- }
-
- // 5b. Find default course section (assuming section=1)
- $section = $DB->get_record('course_sections', [
- 'course' => $params['courseid'],
- 'section' => 1
- ], 'id,sequence');
-
- if (!$section) {
- throw new moodle_exception('sectionnotfound', 'error');
- }
-
- // 5c. Insert a record in mdl_course_modules
- $cm = new stdClass();
- $cm->course = $params['courseid'];
- $cm->module = $module->id;
- $cm->instance = $lessonid;
- $cm->section = $section->id;
- $cm->added = time();
- $cm->visible = 1; // show the lesson
- $cm->visibleold = 1;
- $cm->groupmode = 0;
- $cm->groupingid = 0;
-
- // Activity completion
- $cm->completion = $params['completion'];
-
- // "Display description on course page"
- $cm->showdescription = $params['showdescription'];
-
- $courseModuleId = $DB->insert_record('course_modules', $cm);
- if (!$courseModuleId) {
- throw new moodle_exception('erroraddingmodule', 'local_learninglens');
- }
-
- // 5d. Update the course section sequence
- $sequence = trim($section->sequence . ',' . $courseModuleId, ',');
- $section->sequence = $sequence;
- $DB->update_record('course_sections', $section);
-
- // 6. Rebuild cache
- rebuild_course_cache($params['courseid'], true);
-
- // 7. Return lessonId + courseModuleId
- return [
- 'lessonId' => $lessonid,
- 'courseModuleId' => $courseModuleId,
- ];
- }
-
- public static function execute_returns(): external_single_structure {
- return new external_single_structure([
- 'lessonId' => new external_value(
- PARAM_INT, 'ID of the newly created lesson'
- ),
- 'courseModuleId' => new external_value(
- PARAM_INT, 'ID of the newly inserted course module record'
- ),
- ]);
- }
-}
diff --git a/team_a/MoodlePlugin/learninglens/classes/external/create_quiz.php b/team_a/MoodlePlugin/learninglens/classes/external/create_quiz.php
deleted file mode 100644
index a601f08f..00000000
--- a/team_a/MoodlePlugin/learninglens/classes/external/create_quiz.php
+++ /dev/null
@@ -1,195 +0,0 @@
-libdir . '/externallib.php');
-require_once($CFG->dirroot . '/mod/quiz/lib.php');
-require_once($CFG->dirroot . '/mod/quiz/locallib.php');
-require_once($CFG->libdir . '/gradelib.php');
-require_once($CFG->dirroot . '/question/editlib.php');
-require_once($CFG->dirroot . '/question/engine/lib.php');
-require_once($CFG->dirroot . '/question/lib.php');
-require_once($CFG->dirroot . '/course/lib.php');
-require_once($CFG->dirroot . '/course/modlib.php');
-require_once($CFG->dirroot . '/mod/quiz/report/statistics/report.php');
-
-use external_function_parameters;
-use external_single_structure;
-use external_multiple_structure;
-use external_value;
-use context_system;
-use context_course;
-use context_module;
-use moodle_exception;
-use coding_exception;
-use required_capability_exception;
-use access_manager;
-use external_api;
-use qformat_xml;
-use question_bank;
-use question_edit_contexts;
-
-
-class create_quiz extends \external_api {
-
-public static function execute_parameters(): external_function_parameters {
- return new external_function_parameters(
- array(
- 'courseid' => new external_value(PARAM_INT, 'ID of the course'),
- 'name' => new external_value(PARAM_TEXT, 'Name of the quiz'),
- 'intro' => new external_value(PARAM_RAW, 'Introductory text for the quiz'),
- 'sectionid' => new external_value(PARAM_INT, 'Section ID', VALUE_DEFAULT, 1),
- 'timeopen' => new external_value(PARAM_TEXT, 'Time when the quiz opens', VALUE_DEFAULT, '0'),
- 'timeclose' => new external_value(PARAM_TEXT, 'Time when the quiz closes', VALUE_DEFAULT, '0'),
- )
- );
-}
-
-public static function execute_returns(): external_single_structure {
- return new external_single_structure(
- array(
- 'quizid' => new external_value(PARAM_INT, 'ID of the created quiz')
- )
- );
-}
-
-public static function execute($courseid, $name, $intro, $sectionid=1, $timeopen='0', $timeclose='0'): array {
- global $DB, $USER;
-
- // validate params
- $params = self::validate_parameters(self::execute_parameters(), array(
- 'courseid' => $courseid,
- 'name' => $name,
- 'intro' => $intro,
- 'sectionid' => $sectionid,
- 'timeopen' => $timeopen,
- 'timeclose' => $timeclose
- ));
-
- // set context
- $context = context_course::instance($params['courseid']);
-
- // ensure the user has permission to create a quiz
- self::validate_context($context);
- require_capability('mod/quiz:addinstance', $context);
- require_capability('mod/quiz:manage', $context);
-
- // create the course module
- $module = new \stdClass();
- $module->course = $params['courseid'];
- $module->module = $DB->get_field('modules', 'id', array('name' => 'quiz'));
- $module->instance = 0;
- $module->section = $params['sectionid'];
- $module->visible = 1;
- $module->visibleold = 1;
- $module->groupmode = 0;
- $module->groupingid = 0;
- $module->completion = 0;
- $module->idnumber = $params['sectionid'];
- $module->added = time();
-
- // add the course module
- $module->coursemodule = add_course_module($module);
- if (!$module->coursemodule) {
- throw new moodle_exception('Could not create course module');
- }
-
- // update module ID
- $module->id = $module->coursemodule;
-
- // add course module to section
- \course_add_cm_to_section($params['courseid'], $module->id, $params['sectionid']);
-
-
- // Convert date strings to Unix timestamps
- $timeopen = strtotime($params['timeopen']); // Convert '12 January 2024' to Unix timestamp
- $timeclose = strtotime($params['timeclose']);
-
- // create the quiz module
- $quiz = new \stdClass();
- $quiz->course = $params['courseid'];
- $quiz->name = $params['name'];
- $quiz->intro = '' . $params['intro'] . '
';
- $quiz->introformat = FORMAT_HTML;
- $quiz->timeopen = $timeopen;
- $quiz->timeclose = $timeclose;
- $quiz->preferredbehaviour = 'deferredfeedback';
- $quiz->attempts = 0;
- $quiz->grade = 100;
- $quiz->sumgrades = 1; // this get updated in the add_type_randoms_to_quiz to propertly calculate the sum based on the number of questions.
- $quiz->timelimit = 0;
- $quiz->overduehandling = 'autosubmit';
- $quiz->graceperiod = 0;
- $quiz->timecreated = time();
- $quiz->timemodified = time();
- $quiz->quizpassword = '';
- $quiz->coursemodule = $module->id;
- $quiz->feedbackboundarycount = 0;
- $quiz->feedbacktext = [];
- $quiz->questionsperpage = 1;
- $quiz->shuffleanswers = 1;
- $quiz->browsersecurity = '-';
-
- // process the options from the form.
- $result = quiz_process_options($quiz);
- if ($result && is_string($result)) {
- throw new moodle_exception($result);
- }
-
- // insert the quiz into the database
- $quiz->id = $DB->insert_record('quiz', $quiz);
-
- // update the course module with the quiz instance ID
- $DB->set_field('course_modules', 'instance', $quiz->id, array('id' => $module->id));
-
- // create the first section for this quiz.
- $DB->insert_record('quiz_sections', ['quizid' => $quiz->id, 'firstslot' => 1, 'heading' => '', 'shufflequestions' => 0]);
-
- // clear feedback
- $DB->delete_records('quiz_feedback', ['quizid' => $quiz->id]);
-
- // set feedback
- for ($i = 0; $i <= $quiz->feedbackboundarycount; $i++) {
- if (isset($quiz->feedbacktext[$i])) {
- $feedback = new \stdClass();
- $feedback->quizid = $quiz->id;
- $feedback->feedbacktext = $quiz->feedbacktext[$i]['text'] ?? '';
- $feedback->feedbacktextformat = $quiz->feedbacktext[$i]['format'] ?? FORMAT_HTML;
- $feedback->mingrade = $quiz->feedbackboundaries[$i] ?? 0;
- $feedback->maxgrade = $quiz->feedbackboundaries[$i - 1] ?? 100;
- $feedback->id = $DB->insert_record('quiz_feedback', $feedback);
- $feedbacktext = file_save_draft_area_files((int)$quiz->feedbacktext[$i]['itemid'] ?? 0,
- $context->id, 'mod_quiz', 'feedback', $feedback->id,
- ['subdirs' => false, 'maxfiles' => -1, 'maxbytes' => 0],
- $quiz->feedbacktext[$i]['text'] ?? '');
- $DB->set_field('quiz_feedback', 'feedbacktext', $feedbacktext, ['id' => $feedback->id]);
- }
- }
-
- // store settings belonging to the access rules
- \mod_quiz\access_manager::save_settings($quiz);
-
- // update events related to this quiz
- quiz_update_events($quiz);
- $completionexpected = (!empty($quiz->completionexpected)) ? $quiz->completionexpected : null;
- \core_completion\api::update_completion_date_event($quiz->coursemodule, 'quiz', $quiz->id, $completionexpected);
-
- // update related grade item.
- quiz_grade_item_update($quiz);
-
- // update quiz review fields
- $quiz->reviewattempt = 69888;
- $quiz->reviewcorrectness = 4352;
- $quiz->reviewmaxmarks = 69888;
- $quiz->reviewmarks = 4352;
- $quiz->reviewspecificfeedback = 4352;
- $quiz->reviewgeneralfeedback = 4352;
- $quiz->reviewrightanswer = 4352;
- $quiz->reviewoverallfeedback = 4352;
- $DB->update_record('quiz', $quiz);
-
- // return quiz ID
- return array('quizid' => $quiz->id);
-}
-};
\ No newline at end of file
diff --git a/team_a/MoodlePlugin/learninglens/classes/external/delete_lesson_plan.php b/team_a/MoodlePlugin/learninglens/classes/external/delete_lesson_plan.php
deleted file mode 100644
index 98a1f965..00000000
--- a/team_a/MoodlePlugin/learninglens/classes/external/delete_lesson_plan.php
+++ /dev/null
@@ -1,54 +0,0 @@
-libdir . '/externallib.php');
-
-use external_function_parameters;
-use external_single_structure;
-use external_value;
-use external_api;
-use context_module;
-use context_course;
-
-class delete_lesson_plan extends external_api {
-
- public static function execute_parameters() {
- return new external_function_parameters([
- 'lessonid' => new external_value(PARAM_INT, 'ID of the lesson plan to delete'),
- ]);
- }
-
- public static function execute($lessonid) {
- global $DB, $USER;
-
- $params = self::validate_parameters(self::execute_parameters(), ['lessonid' => $lessonid]);
-
- // Check if the lesson plan exists
- if (!$lesson = $DB->get_record('lesson', ['id' => $lessonid], '*', IGNORE_MISSING)) {
- throw new \moodle_exception('invalidlessonid', 'error', '', $lessonid);
- }
-
- // Get course context
- $context = context_course::instance($lesson->course);
-
- // Ensure user has permission to delete lesson plans
- require_capability('mod/lesson:manage', $context);
-
- // Delete the lesson plan
- $DB->delete_records('lesson', ['id' => $lessonid]);
-
- // Clear cache for the lesson plan
- purge_all_caches();
-
- return ['status' => 'success', 'message' => 'Lesson plan deleted successfully'];
- }
-
- public static function execute_returns() {
- return new external_single_structure([
- 'status' => new external_value(PARAM_TEXT, 'Status of the request'),
- 'message' => new external_value(PARAM_TEXT, 'Message about the operation result'),
- ]);
- }
-}
diff --git a/team_a/MoodlePlugin/learninglens/classes/external/get_all_overrides.php b/team_a/MoodlePlugin/learninglens/classes/external/get_all_overrides.php
deleted file mode 100644
index 5fbe0192..00000000
--- a/team_a/MoodlePlugin/learninglens/classes/external/get_all_overrides.php
+++ /dev/null
@@ -1,99 +0,0 @@
-libdir . '/externallib.php');
-
-use external_function_parameters;
-use external_single_structure;
-use external_multiple_structure;
-use external_value;
-use external_api;
-
-class get_all_overrides extends external_api {
-
- public static function execute_parameters() {
- return new external_function_parameters([]);
- }
-
- public static function execute() {
- global $DB;
-
- $questions = self::get_overrides();
-
- return $questions;
- }
-
- private static function get_overrides() {
- global $DB;
-
- $sql = "
- SELECT
- qo.id AS override_id,
- 'quiz' AS assignment_type,
- qo.quiz AS assignment_id,
- q.name AS assignment_name,
- q.course AS course_id,
- c.fullname AS course_name,
- qo.userid,
- CONCAT(u.firstname, ' ', u.lastname) AS fullname,
- qo.timeopen AS start_time,
- qo.timeclose AS end_time,
- qo.timelimit,
- NULL AS cutoff_time,
- qo.attempts,
- qo.password,
- NULL AS sortorder
- FROM mdl_quiz_overrides qo
- LEFT JOIN mdl_quiz q ON qo.quiz = q.id
- LEFT JOIN mdl_course c ON q.course = c.id
- LEFT JOIN mdl_user u ON qo.userid = u.id
- UNION ALL
- SELECT
- ao.id AS override_id,
- 'essay' AS assignment_type,
- ao.assignid AS assignment_id,
- a.name AS assignment_name,
- a.course AS course_id,
- c.fullname AS course_name,
- ao.userid,
- CONCAT(u.firstname, ' ', u.lastname) AS fullname,
- ao.allowsubmissionsfromdate AS start_time,
- ao.duedate AS end_time,
- ao.timelimit,
- ao.cutoffdate AS cutoff_time,
- NULL AS attempts,
- NULL AS password,
- ao.sortorder
- FROM mdl_assign_overrides ao
- LEFT JOIN mdl_assign a ON ao.assignid = a.id
- LEFT JOIN mdl_course c ON a.course = c.id
- LEFT JOIN mdl_user u ON ao.userid = u.id;
- ";
-
- return $DB->get_records_sql($sql);
- }
-
- public static function execute_returns() {
- return new external_multiple_structure(
- new external_single_structure([
- 'override_id' => new external_value(PARAM_INT, 'Override ID'),
- 'assignment_type' => new external_value(PARAM_TEXT, 'Assignment Type'),
- 'assignment_id' => new external_value(PARAM_INT, 'Assignment ID'),
- 'assignment_name' => new external_value(PARAM_TEXT, 'Assignment Name'),
- 'course_id' => new external_value(PARAM_INT, 'Course ID'),
- 'course_name' => new external_value(PARAM_TEXT, 'Course Name'),
- 'userid' => new external_value(PARAM_INT, 'User ID'),
- 'fullname' => new external_value(PARAM_TEXT, 'Full name'),
- 'start_time' => new external_value(PARAM_INT, 'Time Open'),
- 'end_time' => new external_value(PARAM_INT, 'Time Close'),
- 'timelimit' => new external_value(PARAM_INT, 'Time Limit'),
- 'cutoff_time' => new external_value(PARAM_INT, 'Cutoff Time (Essay only)'),
- 'attempts' => new external_value(PARAM_INT, 'Attempts (Quiz only)'),
- 'password' => new external_value(PARAM_TEXT, 'Password (Quiz only)'),
- 'sortorder' => new external_value(PARAM_INT, 'Sortorder (Essay only)'),
- ])
- );
- }
-}
diff --git a/team_a/MoodlePlugin/learninglens/classes/external/get_lesson_plans_by_course.php b/team_a/MoodlePlugin/learninglens/classes/external/get_lesson_plans_by_course.php
deleted file mode 100644
index 43f7ce32..00000000
--- a/team_a/MoodlePlugin/learninglens/classes/external/get_lesson_plans_by_course.php
+++ /dev/null
@@ -1,65 +0,0 @@
-libdir . '/externallib.php');
-
-use external_function_parameters;
-use external_single_structure;
-use external_multiple_structure;
-use external_value;
-use external_api;
-use context_course;
-
-class get_lesson_plans_by_course extends external_api {
-
- public static function execute_parameters() {
- return new external_function_parameters([
- 'courseid' => new external_value(PARAM_INT, 'ID of the course'),
- ]);
- }
-
- public static function execute($courseid) {
- global $DB;
-
- $params = self::validate_parameters(self::execute_parameters(), ['courseid' => $courseid]);
-
- // Ensure course exists
- if (!$DB->record_exists('course', ['id' => $courseid])) {
- throw new \moodle_exception('invalidcourseid', 'error', '', $courseid);
- }
-
- // Ensure user has the correct capability to view lessons
- $context = context_course::instance($courseid);
- require_capability('mod/lesson:view', $context);
-
- // Fetch lesson plans
- $lessonplans = self::get_lessons_by_course($courseid);
-
- return array_values($lessonplans);
- }
-
- private static function get_lessons_by_course($courseid) {
- global $DB;
-
- $sql = "
- SELECT id, name, intro, timemodified
- FROM {lesson}
- WHERE course = :courseid
- ";
-
- return $DB->get_records_sql($sql, ['courseid' => $courseid]);
- }
-
- public static function execute_returns() {
- return new external_multiple_structure(
- new external_single_structure([
- 'id' => new external_value(PARAM_INT, 'Lesson Plan ID'),
- 'name' => new external_value(PARAM_TEXT, 'Lesson Plan Name'),
- 'intro' => new external_value(PARAM_RAW, 'Lesson Plan Description'),
- 'timemodified' => new external_value(PARAM_INT, 'Last Modified Timestamp'),
- ])
- );
- }
-}
diff --git a/team_a/MoodlePlugin/learninglens/classes/external/get_question_stats_from_quiz.php b/team_a/MoodlePlugin/learninglens/classes/external/get_question_stats_from_quiz.php
deleted file mode 100644
index 7ee2df98..00000000
--- a/team_a/MoodlePlugin/learninglens/classes/external/get_question_stats_from_quiz.php
+++ /dev/null
@@ -1,156 +0,0 @@
-libdir . '/externallib.php');
-
-use external_function_parameters;
-use external_single_structure;
-use external_multiple_structure;
-use external_value;
-use external_api;
-
-class get_question_stats_from_quiz extends external_api {
-
- /**
- * Define parameter structure for the function.
- */
- public static function execute_parameters() {
- return new external_function_parameters([
- 'quizid' => new external_value(PARAM_INT, 'ID of the quiz'),
- ]);
- }
-
- /**
- * Main execution function. This is what gets called by the web service.
- */
- public static function execute($quizid) {
- // Validate incoming parameters.
- $params = self::validate_parameters(
- self::execute_parameters(),
- ['quizid' => $quizid]
- );
-
- // *Optional*: permission checks (capabilities, context, etc.)
-
- // Actually fetch data.
- $records = self::get_question_stats_by_quiz($params['quizid']);
-
- // Return the array of data.
- return $records;
- }
-
- /**
- * The real logic. Query for questions (similar to your existing logic)
- * but also compute correct/incorrect stats or any other analytics.
- */
- private static function get_question_stats_by_quiz($quizid) {
- global $DB;
-
- // 1) Gather the questions (same as before)
- $sql_questions = "
- SELECT q.id,
- q.name,
- q.questiontext,
- q.qtype
- FROM {question} q
- WHERE q.id IN (
- SELECT qbe.id
- FROM {question_bank_entries} qbe
- WHERE qbe.questioncategoryid = (
- SELECT qc.id
- FROM {question_categories} qc
- WHERE (
- SELECT quiz.intro
- FROM {quiz} quiz
- WHERE quiz.id = :quizid
- ) LIKE CONCAT('%', qc.name, '%')
- LIMIT 1
- )
- )
- ";
-
- $questions = $DB->get_records_sql($sql_questions, ['quizid' => $quizid]);
- if (!$questions) {
- return [];
- }
-
- // 2) Gather attempt stats by looking for 'gradedright', 'gradedwrong', 'gradedpartial'
- // instead of 'complete' or fraction-based checks
- $sql_stats = "
- SELECT
- qa.questionid,
- SUM(CASE WHEN qs.state = 'gradedright' THEN 1 ELSE 0 END) AS numcorrect,
- SUM(CASE WHEN qs.state = 'gradedwrong' THEN 1 ELSE 0 END) AS numincorrect,
- SUM(CASE WHEN qs.state = 'gradedpartial' THEN 1 ELSE 0 END) AS numpartial,
- COUNT(*) AS totalattempts
- FROM {quiz_attempts} quiza
- JOIN {question_usages} qu ON qu.id = quiza.uniqueid
- JOIN {question_attempts} qa ON qa.questionusageid = qu.id
- JOIN {question_attempt_steps} qs ON qs.questionattemptid = qa.id
- AND qs.sequencenumber = (
- SELECT MAX(qs2.sequencenumber)
- FROM {question_attempt_steps} qs2
- WHERE qs2.questionattemptid = qa.id
- )
- WHERE quiza.quiz = :quizid2
- GROUP BY qa.questionid
- ";
-
- $statsrecords = $DB->get_records_sql($sql_stats, ['quizid2' => $quizid]);
-
- // 3) Merge stats with the question array
- $results = [];
- foreach ($questions as $q) {
- $qid = $q->id;
-
- // Default zeros
- $correct = 0;
- $incorrect = 0;
- $partial = 0;
- $total = 0;
-
- if (isset($statsrecords[$qid])) {
- $record = $statsrecords[$qid];
- $correct = (int) $record->numcorrect;
- $incorrect = (int) $record->numincorrect;
- $partial = (int) $record->numpartial;
- $total = (int) $record->totalattempts;
- }
-
- $results[] = [
- 'id' => $qid,
- 'name' => $q->name,
- 'questiontext' => $q->questiontext,
- 'qtype' => $q->qtype,
- 'numcorrect' => $correct,
- 'numincorrect' => $incorrect,
- 'numpartial' => $partial,
- 'totalattempts'=> $total,
- ];
- }
-
- return $results;
- }
-
-
- /**
- * Define the return structure.
- */
- public static function execute_returns() {
- return new external_multiple_structure(
- new external_single_structure([
- 'id' => new external_value(PARAM_INT, 'Question ID'),
- 'name' => new external_value(PARAM_TEXT, 'Question name'),
- 'questiontext' => new external_value(PARAM_RAW, 'Question text'),
- 'qtype' => new external_value(PARAM_ALPHANUMEXT, 'Question type'),
- 'numcorrect' => new external_value(PARAM_INT, 'Number of fully correct attempts'),
- 'numincorrect' => new external_value(PARAM_INT, 'Number of fully incorrect attempts'),
- 'numpartial' => new external_value(PARAM_INT, 'Number of partially correct attempts'),
- 'totalattempts' => new external_value(PARAM_INT, 'Total attempts (final steps)'),
- ])
- );
- }
-
-}
diff --git a/team_a/MoodlePlugin/learninglens/classes/external/get_questions_from_quiz.php b/team_a/MoodlePlugin/learninglens/classes/external/get_questions_from_quiz.php
deleted file mode 100644
index b884c1e0..00000000
--- a/team_a/MoodlePlugin/learninglens/classes/external/get_questions_from_quiz.php
+++ /dev/null
@@ -1,66 +0,0 @@
-libdir . '/externallib.php');
-
-use external_function_parameters;
-use external_single_structure;
-use external_multiple_structure;
-use external_value;
-use external_api;
-
-class get_questions_from_quiz extends external_api {
-
- public static function execute_parameters() {
- return new external_function_parameters([
- 'quizid' => new external_value(PARAM_INT, 'ID of the quiz'),
- ]);
- }
-
- public static function execute($quizid) {
- global $DB;
-
- $params = self::validate_parameters(self::execute_parameters(), ['quizid' => $quizid]);
-
- $questions = self::get_questions_by_quiz($quizid);
-
- return $questions;
- }
-
- private static function get_questions_by_quiz($quizid) {
- global $DB;
-
- $sql = "
- SELECT q.id, q.name, q.questiontext, q.qtype
- FROM {question} q
- WHERE q.id IN (
- SELECT qbe.id
- FROM {question_bank_entries} qbe
- WHERE qbe.questioncategoryid = (
- SELECT qc.id
- FROM {question_categories} qc
- WHERE (
- SELECT quiz.intro
- FROM {quiz} quiz
- WHERE quiz.id = :quizid
- ) LIKE CONCAT('%', qc.name, '%')
- LIMIT 1
- )
- )";
-
- return $DB->get_records_sql($sql, ['quizid' => $quizid]);
- }
-
- public static function execute_returns() {
- return new external_multiple_structure(
- new external_single_structure([
- 'id' => new external_value(PARAM_INT, 'Question ID'),
- 'name' => new external_value(PARAM_TEXT, 'Question name'),
- 'questiontext' => new external_value(PARAM_RAW, 'Question text'),
- 'qtype' => new external_value(PARAM_ALPHANUMEXT, 'Question type'),
- ])
- );
- }
-}
diff --git a/team_a/MoodlePlugin/learninglens/classes/external/get_rubric.php b/team_a/MoodlePlugin/learninglens/classes/external/get_rubric.php
deleted file mode 100644
index 256bfb4c..00000000
--- a/team_a/MoodlePlugin/learninglens/classes/external/get_rubric.php
+++ /dev/null
@@ -1,102 +0,0 @@
-libdir . '/externallib.php');
-require_once("$CFG->dirroot/grade/grading/lib.php");
-require_once("$CFG->dirroot/mod/assign/locallib.php");
-
-use external_function_parameters;
-use external_single_structure;
-use external_multiple_structure;
-use external_value;
-use context_module;
-use external_api;
-
-class get_rubric extends external_api {
-
- public static function execute_parameters() {
- return new external_function_parameters([
- 'assignmentid' => new external_value(PARAM_INT, 'ID of the assignment'),
- ]);
- }
-
- public static function execute($assignmentid) {
- global $USER;
-
- // Validate the parameters.
- $params = self::validate_parameters(self::execute_parameters(), [
- 'assignmentid' => $assignmentid,
- ]);
-
- // Fetch the definition ID for the given assignment.
- // $definitionid = self::get_definitionid_from_assignmentid($assignmentid);
- $rubricdata[] = self::get_definitionid_from_assignmentid($assignmentid);
- return $rubricdata;
- }
-
- public static function get_definitionid_from_assignmentid($assignmentid) {
- global $DB;
-
- //Get the course module ID (`cmid`) for the assignment.
- $cmid = $DB->get_field_sql("
- SELECT cm.id
- FROM {course_modules} cm
- JOIN {modules} m ON m.id = cm.module
- JOIN {assign} a ON a.id = cm.instance
- WHERE m.name = 'assign' AND a.id = ?", [$assignmentid]);
-
- if (!$cmid) {
- throw new \moodle_exception('invalidassignmentid', 'error', '', $assignmentid);
- }
-
- //Fetch the context of the assignment using the `cmid`.
- $context = context_module::instance($cmid);
-
- //Identify the component. For assignments, it is usually 'mod_assign'.
- $component = 'mod_assign';
-
- //Initialize the grading manager without context first.
- $gradingmanager = new \grading_manager();
- $gradingmanager->set_context($context);
- $gradingmanager->set_component($component);
-
- //Get available grading areas for the specified component and context.
- $areas = $gradingmanager->get_available_areas();
- if (!in_array('submissions', array_keys($areas))) {
- return null; // 'submissions' grading area not found.
- }
- $gradingmanager->set_area('submissions');
-
- //Get the controller for the 'submissions' grading area.
- $controller = $gradingmanager->get_controller('rubric');
-
- //Check if the grading method is 'rubric' and get the definition ID.
- if (!is_null($controller)) {
- return $controller->get_definition();
- }
- return null;
- }
-
- public static function execute_returns() {
- return new external_multiple_structure(
- new external_single_structure([
- 'id' => new external_value(PARAM_INT, 'Instance ID'),
- 'rubric_criteria' => new external_multiple_structure(
- new external_single_structure([
- 'id' => new external_value(PARAM_INT, 'Criterion ID'),
- 'description' => new external_value(PARAM_TEXT, 'Criterion description'),
- 'levels' => new external_multiple_structure(
- new external_single_structure([
- 'id' => new external_value(PARAM_INT, 'Level ID'),
- 'score' => new external_value(PARAM_FLOAT, 'Level score'),
- 'definition' => new external_value(PARAM_TEXT, 'Level definition'),
- ])
- ),
- ])
- ),
- ])
- );
- }
-}
diff --git a/team_a/MoodlePlugin/learninglens/classes/external/get_rubric_grades.php b/team_a/MoodlePlugin/learninglens/classes/external/get_rubric_grades.php
deleted file mode 100644
index 5c50f84e..00000000
--- a/team_a/MoodlePlugin/learninglens/classes/external/get_rubric_grades.php
+++ /dev/null
@@ -1,107 +0,0 @@
-libdir . '/externallib.php');
- require_once("$CFG->dirroot/grade/grading/lib.php");
- require_once("$CFG->dirroot/mod/assign/locallib.php");
-
- use external_function_parameters;
- use external_single_structure;
- use external_multiple_structure;
- use external_value;
- use context_module;
- use external_api;
-
-class get_rubric_grades extends external_api {
-
- public static function execute_parameters() {
- return new external_function_parameters([
- 'assignmentid' => new external_value(PARAM_INT, 'ID of the assignment'),
- 'userid' => new external_value(PARAM_INT, 'ID of the user')
- ]);
- }
-
- // Define the return structure for the function.
- public static function execute_returns() {
- return new external_multiple_structure(
- new external_single_structure(
- array(
- 'criterionid' => new external_value(PARAM_INT, 'The ID of the criterion'),
- 'criterion_description' => new external_value(PARAM_TEXT, 'The description of the criterion'),
- 'levelid' => new external_value(PARAM_INT, 'The ID of the level'),
- 'level_description' => new external_value(PARAM_TEXT, 'The description of the level in the criterion'),
- 'score' => new external_value(PARAM_FLOAT, 'The score for the criterion'),
- 'remark' => new external_value(PARAM_RAW, 'Remarks by the grader')
- )
- )
- );
- }
- public static function execute($assignmentid, $userid) {
- global $USER;
-
- // Validate the parameters.
- $params = self::validate_parameters(self::execute_parameters(), [
- 'assignmentid' => $assignmentid,
- 'userid' => $userid
- ]);
-
- global $DB;
-
- // Get the assignment's grading instance ID.
- $itemid = $DB->get_field('assign_grades', 'id', ['assignment' => $assignmentid, 'userid' => $userid], MUST_EXIST);
-
- // Add conditions to ensure you get only one grading instance.
- $gradinginstances = $DB->get_records('grading_instances', ['itemid' => $itemid]);
-
-
- $valid_gradinginstance = null;
- // Loop through grading instances to find the one with status = 1 and method = 'rubric'.
- foreach ($gradinginstances as $instance) {
- if ($instance->status == 1) {
- // Get the definition for the grading instance.
- $definition = $DB->get_record('grading_definitions', ['id' => $instance->definitionid], '*', IGNORE_MISSING);
- if ($definition && $definition->method === 'rubric') {
- $valid_gradinginstance = $instance;
- break; // Stop once we find the valid instance.
- }
- }
- }
-
- if (!$valid_gradinginstance) {
- throw new moodle_exception('norubricinstance', 'error', '', $assignmentid);
- }
-
- // Query the rubric fillings for this submission.
- $rubricgrades = $DB->get_records('gradingform_rubric_fillings', ['instanceid' => $valid_gradinginstance->id]);
-
- $rubricdata = [];
-
- foreach ($rubricgrades as $grade) {
- $criterion = $DB->get_record('gradingform_rubric_criteria', ['id' => $grade->criterionid], '*', MUST_EXIST);
- $level = $DB->get_record('gradingform_rubric_levels', ['id' => $grade->levelid], '*', MUST_EXIST);
- $rubricdata[] = [
- 'criterion_description' => $criterion->description,
- 'level_description' => $level->definition,
- 'score' => $level->score,
- 'remark' => $grade->remark,
- 'criterionid' => $criterion->id,
- 'levelid' => $level->id
- ];
- }
-
- return $rubricdata;
-
- }
-}
\ No newline at end of file
diff --git a/team_a/MoodlePlugin/learninglens/classes/external/import_questions.php b/team_a/MoodlePlugin/learninglens/classes/external/import_questions.php
deleted file mode 100644
index 13e9455b..00000000
--- a/team_a/MoodlePlugin/learninglens/classes/external/import_questions.php
+++ /dev/null
@@ -1,92 +0,0 @@
- new external_value(PARAM_INT, 'The course ID'),
- 'questionxml' => new external_value(PARAM_RAW, 'Question XML'),
- ]);
- }
-
- /**
- * Defines function return values
- * @return external_single_structure
- */
- public static function execute_returns(): external_single_structure {
- return new external_single_structure(
- array(
- 'categoryid' => new external_value(PARAM_INT, 'ID of the created category')
- )
- );
- }
-
- /**
- * Execute the function.
- * @param $courseid
- * @param $questionxml
- * @return array
- * @throws \invalid_parameter_exception
- * @throws \moodle_exception
- */
- public static function execute($courseid, $questionxml) {
- global $CFG;
- require_once("$CFG->dirroot/lib/questionlib.php");
- require_once("$CFG->dirroot/lib/datalib.php");
- require_once("$CFG->dirroot/question/format/xml/format.php");
-
- $params = self::validate_parameters(self::execute_parameters(), [
- 'courseid' => $courseid,
- 'questionxml' => $questionxml,
- ]);
-
- // write XML data to temp file
- $tmpfile = tmpfile();
- fwrite($tmpfile, $questionxml);
- $metadata = stream_get_meta_data($tmpfile);
- $tmpfilename = $metadata['uri'];
-
- // Set up the import formatter
- $qformat = new qformat_xml();
- $qformat->setContexts((new question_edit_contexts(context_course::instance($courseid)))->all());
- $qformat->setCourse(get_course($courseid));
- $qformat->setFilename($tmpfilename);
- $qformat->setMatchgrades('error');
- $qformat->setCatfromfile(1);
- $qformat->setContextfromfile(1);
- $qformat->setStoponerror(1);
- $qformat->setCattofile(1);
- $qformat->setContexttofile(1);
- $qformat->set_display_progress(false);
-
- // Do the import
- $imported = $qformat->importprocess();
-
- // // Return list of question IDs
- // $retval = array();
- // for ($i = 0; $i < count($qformat->questionids); $i++) {
- // $retval[$i] = ['questionid' => $qformat->questionids[$i]];
- // }
- // return $retval;
-
- // Return Category ID
- return array('categoryid' => $qformat->category->id);
-
- }
-}
\ No newline at end of file
diff --git a/team_a/MoodlePlugin/learninglens/classes/external/update_lesson_plan.php b/team_a/MoodlePlugin/learninglens/classes/external/update_lesson_plan.php
deleted file mode 100644
index b33f8087..00000000
--- a/team_a/MoodlePlugin/learninglens/classes/external/update_lesson_plan.php
+++ /dev/null
@@ -1,76 +0,0 @@
-libdir . '/externallib.php');
-
-use external_function_parameters;
-use external_single_structure;
-use external_value;
-use external_api;
-use context_course;
-
-class update_lesson_plan extends external_api {
-
- public static function execute_parameters() {
- return new external_function_parameters([
- 'lessonid' => new external_value(PARAM_INT, 'ID of the lesson plan to update'),
- 'name' => new external_value(PARAM_TEXT, 'Updated lesson plan name', VALUE_OPTIONAL),
- 'intro' => new external_value(PARAM_RAW, 'Updated lesson plan description', VALUE_OPTIONAL),
- 'available' => new external_value(PARAM_INT, 'Updated available timestamp', VALUE_OPTIONAL),
- 'deadline' => new external_value(PARAM_INT, 'Updated deadline timestamp', VALUE_OPTIONAL),
- ]);
- }
-
- public static function execute($lessonid, $name = null, $intro = null, $available = null, $deadline = null) {
- global $DB, $USER;
-
- $params = self::validate_parameters(self::execute_parameters(), [
- 'lessonid' => $lessonid,
- 'name' => $name,
- 'intro' => $intro,
- 'available' => $available,
- 'deadline' => $deadline
- ]);
-
- // Check if the lesson plan exists
- if (!$lesson = $DB->get_record('lesson', ['id' => $lessonid], '*', IGNORE_MISSING)) {
- throw new \moodle_exception('invalidlessonid', 'error', '', $lessonid);
- }
-
- // Get course context
- $context = context_course::instance($lesson->course);
-
- // Ensure user has permission to update lesson plans
- require_capability('mod/lesson:manage', $context);
-
- // Prepare update data
- $updateData = ['id' => $lessonid];
-
- if (!is_null($name)) {
- $updateData['name'] = $name;
- }
- if (!is_null($intro)) {
- $updateData['intro'] = $intro;
- }
- if (!is_null($available)) {
- $updateData['available'] = $available;
- }
- if (!is_null($deadline)) {
- $updateData['deadline'] = $deadline;
- }
-
- // Perform update
- $DB->update_record('lesson', (object)$updateData);
-
- return ['status' => 'success', 'message' => 'Lesson plan updated successfully'];
- }
-
- public static function execute_returns() {
- return new external_single_structure([
- 'status' => new external_value(PARAM_TEXT, 'Status of the request'),
- 'message' => new external_value(PARAM_TEXT, 'Message about the operation result'),
- ]);
- }
-}
diff --git a/team_a/MoodlePlugin/learninglens/classes/external/write_grades.php b/team_a/MoodlePlugin/learninglens/classes/external/write_grades.php
deleted file mode 100644
index 38e39737..00000000
--- a/team_a/MoodlePlugin/learninglens/classes/external/write_grades.php
+++ /dev/null
@@ -1,110 +0,0 @@
-libdir . '/externallib.php');
-require_once("$CFG->dirroot/grade/grading/lib.php");
-require_once("$CFG->dirroot/mod/assign/locallib.php");
-
-use external_function_parameters;
-use external_single_structure;
-use external_value;
-use context_module;
-use external_api;
-
-class write_grades extends external_api {
-
- public static function execute_parameters() {
- return new external_function_parameters([
- 'assignmentid' => new external_value(PARAM_INT, 'ID of the assignment'),
- 'userid' => new external_value(PARAM_INT, 'ID of the user'),
- 'grade' => new external_value(PARAM_INT, 'Raw grade value')
- ]);
- }
-
- // Define the return structure for the function.
- public static function execute_returns() {
- return new external_value(PARAM_BOOL, 'True if the grades were successfully written.');
- }
-
- public static function execute($assignmentid, $userid, $grade, $comment = '') {
- global $DB, $USER;
-
- // Validate the parameters.
- $params = self::validate_parameters(self::execute_parameters(), [
- 'assignmentid' => $assignmentid,
- 'userid' => $userid,
- 'grade' => $grade
- ]);
-
-
- // Check if there is already a record in the assign_grades table for this user and assignment.
- $grade_record = $DB->get_record('assign_grades', ['assignment' => $assignmentid, 'userid' => $userid]);
-
- // If no grade record exists, insert a new one.
- if (!$grade_record) {
- $new_grade = new \stdClass();
- $new_grade->assignment = $assignmentid;
- $new_grade->userid = $userid;
- $new_grade->grader = $USER->id; // Set the current user as the grader.
- $new_grade->timecreated = time();
- $new_grade->timemodified = time();
- $new_grade->grade = -1; // Initial placeholder grade (to be updated later).
- $new_grade->attemptnumber = 0; // Assuming this is the first attempt.
- $new_grade->id = $DB->insert_record('assign_grades', $new_grade);
- $itemid = $new_grade->id; // Use the newly inserted record's ID as the itemid.
- } else {
- // Use the existing grade record's ID as the itemid.
- $itemid = $grade_record->id;
- }
-
- // Get the course module for the assignment.
- $course_module_id = $DB->get_field('course_modules', 'id', [
- 'instance' => $assignmentid,
- 'module' => $DB->get_field('modules', 'id', ['name' => 'assign'])
- ]);
-
- if (!$course_module_id) {
- throw new \moodle_exception('nocoursemodule', 'error', '', 'Course module not found for assignment.');
- }
-
- // Get the assignment's grading rubric controller.
- $context = context_module::instance($course_module_id);
- $gradingmanager = get_grading_manager($context, 'mod_assign', 'submissions');
-
-
-
- // Update the grade in the assign_grades table to reflect the final state.
- $finalgrade = $DB->get_record('assign_grades', ['id' => $itemid], '*', MUST_EXIST);
- $finalgrade->grader = $USER->id;
- $finalgrade->grade = $grade;
- $finalgrade->timemodified = time();
- $DB->update_record('assign_grades', $finalgrade);
-
-
- $gradeupdate = new \stdClass();
- $gradeupdate->id = $finalgrade->id;
- $gradeupdate->assignment = $assignmentid;
- $gradeupdate->userid = $userid;
- $gradeupdate->grade = $grade;
- $gradeupdate->rawgrade = $grade;
- $gradeupdate->grader = $USER->id;
- $gradeupdate->timecreated = $finalgrade->timecreated;
- $gradeupdate->timemodified = time();
- $assign = new \assign($context, null, null);
- $assign->update_grade($gradeupdate);
-
- return true;
- }
-
-}
diff --git a/team_a/MoodlePlugin/learninglens/classes/external/write_rubric_grades.php b/team_a/MoodlePlugin/learninglens/classes/external/write_rubric_grades.php
deleted file mode 100644
index 856a8120..00000000
--- a/team_a/MoodlePlugin/learninglens/classes/external/write_rubric_grades.php
+++ /dev/null
@@ -1,199 +0,0 @@
-libdir . '/externallib.php');
-require_once("$CFG->dirroot/grade/grading/lib.php");
-require_once("$CFG->dirroot/mod/assign/locallib.php");
-require_once($CFG->dirroot . '/grade/grading/form/rubric/lib.php');
-
-use external_function_parameters;
-use external_single_structure;
-use external_value;
-use context_module;
-use external_api;
-
-class write_rubric_grades extends external_api {
-
- public static function execute_parameters() {
- return new external_function_parameters([
- 'assignmentid' => new external_value(PARAM_INT, 'ID of the assignment'),
- 'userid' => new external_value(PARAM_INT, 'ID of the user'),
- 'rubricgrades' => new external_value(PARAM_RAW, 'Rubric grading data in JSON string format')
- ]);
- }
-
- // Define the return structure for the function.
- public static function execute_returns() {
- return new external_value(PARAM_BOOL, 'True if the rubric grades were successfully written.');
- }
-
- public static function execute($assignmentid, $userid, $rubricgrades, $comment = '') {
- global $DB, $USER;
-
- // Validate the parameters.
- $params = self::validate_parameters(self::execute_parameters(), [
- 'assignmentid' => $assignmentid,
- 'userid' => $userid,
- 'rubricgrades' => $rubricgrades
- ]);
-
- // Decode the JSON string into an array.
- $rubricgrades_array = json_decode($rubricgrades, true);
-
- // Check if the JSON decoding was successful.
- if (json_last_error() !== JSON_ERROR_NONE) {
- throw new \moodle_exception('invalidjson', 'error', '', json_last_error_msg());
- }
-
- // Check if there is already a record in the assign_grades table for this user and assignment.
- $grade_record = $DB->get_record('assign_grades', ['assignment' => $assignmentid, 'userid' => $userid]);
-
- // If no grade record exists, insert a new one.
- if (!$grade_record) {
- $new_grade = new \stdClass();
- $new_grade->assignment = $assignmentid;
- $new_grade->userid = $userid;
- $new_grade->grader = $USER->id; // Set the current user as the grader.
- $new_grade->timecreated = time();
- $new_grade->timemodified = time();
- $new_grade->grade = -1; // Initial placeholder grade (to be updated later).
- $new_grade->attemptnumber = 0; // Assuming this is the first attempt.
- $new_grade->id = $DB->insert_record('assign_grades', $new_grade);
- $itemid = $new_grade->id; // Use the newly inserted record's ID as the itemid.
- } else {
- // Use the existing grade record's ID as the itemid.
- $itemid = $grade_record->id;
- }
-
- // Get the course module for the assignment.
- $course_module_id = $DB->get_field('course_modules', 'id', [
- 'instance' => $assignmentid,
- 'module' => $DB->get_field('modules', 'id', ['name' => 'assign'])
- ]);
-
- if (!$course_module_id) {
- throw new \moodle_exception('nocoursemodule', 'error', '', 'Course module not found for assignment.');
- }
-
- // Get the assignment's grading rubric controller.
- $context = context_module::instance($course_module_id);
- $gradingmanager = get_grading_manager($context, 'mod_assign', 'submissions');
-
- // Check if the assignment is using the rubric grading method.
- if (!$gradingmanager->get_active_method() || $gradingmanager->get_active_method() !== 'rubric') {
- throw new \moodle_exception('norubriccontroller', 'error', '', 'This assignment is not using the rubric grading method.');
- }
-
- $controller = $gradingmanager->get_controller('rubric');
-
- // Check if there's already an existing grading instance for the rater and item.
- $gradinginstance = $controller->get_current_instance($USER->id, $itemid);
-
-
- if (!$gradinginstance) {
- // If no instance exists, create a new one.
- $gradinginstance = $controller->create_instance($USER->id, $itemid);
- }
-
- // Get the instance ID for further operations.
- $instanceid = $gradinginstance->get_data('id');
-
- // Initialize variables to calculate the total score and maximum score.
- $total_score = 0;
- $max_score = 0;
-
- // Get the rubric definition from the grading controller, which contains the criteria and levels.
- $rubricdefinition = $controller->get_definition();
- // Now get the criteria from the rubric definition.
- $rubriccriteria = $DB->get_records('gradingform_rubric_criteria', ['definitionid' => $rubricdefinition->id]);
-
-
- // Calculate the maximum possible score based on the full rubric definition.
- foreach ($rubriccriteria as $criterion) {
- $maxlevel = $DB->get_record_sql(
- 'SELECT MAX(score) as maxscore FROM {gradingform_rubric_levels} WHERE criterionid = ?',
- [$criterion->id]
- );
- $max_score += $maxlevel->maxscore;
- }
-
- // Write rubric grades for each criterion and calculate the total score for the graded criteria.
- foreach ($rubricgrades_array as $grade) {
- // Ensure the criterion and level exist.
- $criterion = $DB->get_record('gradingform_rubric_criteria', ['id' => $grade['criterionid']], '*', MUST_EXIST);
- $level = $DB->get_record('gradingform_rubric_levels', ['id' => $grade['levelid'], 'criterionid' => $criterion->id], '*', MUST_EXIST);
-
- // Add the selected level score to the total score.
- $total_score += $level->score;
-
- // Check if there's already an entry for this instance and criterion, update or insert as necessary.
- $existinggrade = $DB->get_record('gradingform_rubric_fillings', ['instanceid' => $gradinginstance->get_data('id'), 'criterionid' => $criterion->id]);
-
- if ($existinggrade) {
- // Update existing grade.
- $existinggrade->levelid = $grade['levelid'];
- $existinggrade->remark = $grade['remark'];
- $existinggrade->raterid = $USER->id; // Assuming the current user is the rater.
- $DB->update_record('gradingform_rubric_fillings', $existinggrade);
- } else {
- // Insert new grade.
- $newgrade = new \stdClass();
- $newgrade->instanceid = $gradinginstance->get_data('id'); // Use correct instanceid
- $newgrade->criterionid = $criterion->id;
- $newgrade->levelid = $grade['levelid'];
- $newgrade->remark = $grade['remark'];
- $newgrade->raterid = $USER->id; // Assuming the current user is the rater.
- $DB->insert_record('gradingform_rubric_fillings', $newgrade);
- }
- }
-
- // Create a new object for updating the grading instance's status.
- $instanceupdate = new \stdClass();
- $instanceupdate->id = $gradinginstance->get_data('id');
- $instanceupdate->status = 1; // 1 means "finalized".
- $DB->update_record('grading_instances', $instanceupdate);
-
- // Delete any unsubmitted (status = 0) grading instances for this user and item.
- $DB->delete_records('grading_instances', ['itemid' => $itemid, 'raterid' => $USER->id, 'status' => 0]);
-
- // Get the maximum grade for the assignment
- $assignment = $DB->get_record('assign', ['id' => $assignmentid], '*', MUST_EXIST);
- $maxgrade = $assignment->grade; // Maximum possible grade
-
- // Calculate the final grade as a proportion of the total score to the maximum possible score.
- $finalgradevalue = ($total_score / $max_score) * $maxgrade;
-
- // Update the grade in the assign_grades table to reflect the final state.
- $finalgrade = $DB->get_record('assign_grades', ['id' => $itemid], '*', MUST_EXIST);
- $finalgrade->grader = $USER->id;
- $finalgrade->grade = $finalgradevalue;
- $finalgrade->timemodified = time();
- $DB->update_record('assign_grades', $finalgrade);
-
- $gradeupdate = new \stdClass();
- $gradeupdate->id = $finalgrade->id;
- $gradeupdate->assignment = $assignmentid;
- $gradeupdate->userid = $userid;
- $gradeupdate->grade = $finalgradevalue;
- $gradeupdate->rawgrade = $finalgradevalue;
- $gradeupdate->grader = $USER->id;
- $gradeupdate->timecreated = $finalgrade->timecreated;
- $gradeupdate->timemodified = time();
- $assign = new \assign($context, null, null);
- $assign->update_grade($gradeupdate);
-
- return true;
- }
-
-}
diff --git a/team_a/MoodlePlugin/learninglens/db/.DS_Store b/team_a/MoodlePlugin/learninglens/db/.DS_Store
deleted file mode 100644
index 5008ddfc..00000000
Binary files a/team_a/MoodlePlugin/learninglens/db/.DS_Store and /dev/null differ
diff --git a/team_a/MoodlePlugin/learninglens/db/access.php b/team_a/MoodlePlugin/learninglens/db/access.php
deleted file mode 100644
index 62fabc80..00000000
--- a/team_a/MoodlePlugin/learninglens/db/access.php
+++ /dev/null
@@ -1,15 +0,0 @@
- array(
- 'riskbitmask' => RISK_SPAM | RISK_XSS,
-
- 'captype' => 'write',
- 'contextlevel' => CONTEXT_COURSE,
- 'archetypes' => array(
- 'teacher' => CAP_ALLOW,
- 'editingteacher' => CAP_ALLOW,
- ),
- ),
-);
\ No newline at end of file
diff --git a/team_a/MoodlePlugin/learninglens/db/services.php b/team_a/MoodlePlugin/learninglens/db/services.php
deleted file mode 100644
index bb94928c..00000000
--- a/team_a/MoodlePlugin/learninglens/db/services.php
+++ /dev/null
@@ -1,160 +0,0 @@
- [
- 'classname' => 'local_learninglens\external\create_quiz',
- 'description' => 'Create a quiz in a specified course',
- 'type' => 'write',
- 'ajax' => true,
- 'capabilities' => 'mod/quiz:addinstance',
- 'services' => [
- MOODLE_OFFICIAL_MOBILE_SERVICE,
- ]
- ],
-
- 'local_learninglens_import_questions' => [
- 'classname' => 'local_learninglens\external\import_questions',
- 'description' => 'Import XML questions to a course question bank',
- 'type' => 'write',
- 'ajax' => true,
- 'capabilities' => 'moodle/question:add',
- 'services' => [
- MOODLE_OFFICIAL_MOBILE_SERVICE,
- ]
- ],
- 'local_learninglens_add_type_randoms_to_quiz' => [
- 'classname' => 'local_learninglens\external\add_type_randoms_to_quiz',
- 'description' => 'Add questions of type random to quiz from category',
- 'type' => 'write',
- 'ajax' => true,
- 'capabilities' => 'mod/quiz:manage',
- 'services' => [
- MOODLE_OFFICIAL_MOBILE_SERVICE,
- ]
- ],
- 'local_learninglens_get_rubric' => [
- 'classname' => 'local_learninglens\external\get_rubric',
- 'description' => 'Returns instances of grading forms including rubrics.',
- 'type' => 'read',
- 'ajax' => true,
- 'capabilities' => 'moodle/grade:view',
- 'services' => [
- MOODLE_OFFICIAL_MOBILE_SERVICE,
- ]
- ],
- 'local_learninglens_create_assignment' => [
- 'classname' => 'local_learninglens\external\create_assignment',
- 'description' => 'Creates an assignment and optionally attach a rubric.',
- 'type' => 'write',
- 'ajax' => true,
- 'capabilities' => 'moodle/course:manageactivities',
- 'services' => [
- MOODLE_OFFICIAL_MOBILE_SERVICE,
- ]
- ],
- 'local_learninglens_get_rubric_grades' => [
- 'classname' => 'local_learninglens\external\get_rubric_grades',
- 'description' => 'Gets rubric grades for a specific submission.',
- 'type' => 'read',
- 'ajax' => true,
- 'capabilities' => 'mod/assign:grade',
- 'services' => [
- MOODLE_OFFICIAL_MOBILE_SERVICE,
- ]
- ],
- 'local_learninglens_write_rubric_grades' => [
- 'classname' => 'local_learninglens\external\write_rubric_grades',
- 'description' => 'Sets rubric grades for a specific submission.',
- 'type' => 'write',
- 'ajax' => true,
- 'capabilities' => 'mod/assign:grade',
- 'services' => [
- MOODLE_OFFICIAL_MOBILE_SERVICE,
- ]
- ],
- 'local_learninglens_write_grades' => [
- 'classname' => 'local_learninglens\external\write_grades',
- 'description' => 'Sets grades for a specific submission.',
- 'type' => 'write',
- 'ajax' => true,
- 'capabilities' => 'mod/assign:grade',
- 'services' => [
- MOODLE_OFFICIAL_MOBILE_SERVICE,
- ]
- ],
- 'local_learninglens_create_lesson' => [
- 'classname' => 'local_learninglens\external\create_lesson',
- 'description' => 'Creates a new lesson in a Moodle course and links it to the course modules',
- 'type' => 'write',
- 'ajax' => true,
- 'capabilities' => 'mod/lesson:addinstance',
- 'services' => [
- MOODLE_OFFICIAL_MOBILE_SERVICE,
- ]
- ],
- 'local_learninglens_get_questions_from_quiz' => [
- 'classname' => 'local_learninglens\external\get_questions_from_quiz',
- 'description' => 'Fetches questions linked to a quiz ID',
- 'type' => 'read',
- 'ajax' => true,
- 'capabilities'=> 'mod/quiz:view',
- 'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],
- ],
- 'local_learninglens_add_quiz_override' => [
- 'classname' => 'local_learninglens\external\add_quiz_override',
- 'description' => 'Adds an override to a quiz for a user or group.',
- 'type' => 'write',
- 'ajax' => true,
- 'capabilities'=> 'mod/quiz:manage',
- 'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],
- ],
- 'local_learninglens_add_essay_override' => [
- 'classname' => 'local_learninglens\external\add_essay_override',
- 'description' => 'Adds an override to a essay for a user or group.',
- 'type' => 'write',
- 'ajax' => true,
- 'capabilities'=> 'mod/assign:manage',
- 'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],
- ],
- 'local_learninglens_get_lesson_plans_by_course' => [
- 'classname' => 'local_learninglens\external\get_lesson_plans_by_course',
- 'description' => 'Retrieves lesson plans associated with a given course ID.',
- 'type' => 'read',
- 'ajax' => true,
- 'capabilities' => 'mod/lesson:view',
- 'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE] // Enables use in the Moodle Mobile App
- ],
- 'local_learninglens_delete_lesson_plan' => [
- 'classname' => 'local_learninglens\external\delete_lesson_plan',
- 'description' => 'Deletes a lesson plan by ID.',
- 'type' => 'write',
- 'ajax' => true,
- 'capabilities' => 'mod/lesson:manage',
- 'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE]
- ],
- 'local_learninglens_update_lesson_plan' => [
- 'classname' => 'local_learninglens\external\update_lesson_plan',
- 'description' => 'Updates an existing lesson plan by ID.',
- 'type' => 'write',
- 'ajax' => true,
- 'capabilities' => 'mod/lesson:manage',
- 'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE]
- ],
- 'local_learninglens_get_all_overrides' => [
- 'classname' => 'local_learninglens\external\get_all_overrides',
- 'description' => 'Gets overrides for both quiz and essay',
- 'type' => 'read',
- 'ajax' => true,
- 'capabilities' => 'mod/quiz:view, mod/assign:view',
- 'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE]
- ],
- 'local_learninglens_get_question_stats_from_quiz' => [
- 'classname' => 'local_learninglens\external\get_question_stats_from_quiz',
- 'description' => 'Fetches questions + stats (correct/incorrect counts) for a quiz',
- 'type' => 'read',
- 'ajax' => true,
- 'capabilities'=> 'mod/quiz:view',
- 'services' => [MOODLE_OFFICIAL_MOBILE_SERVICE],
- ],
-];
\ No newline at end of file
diff --git a/team_a/MoodlePlugin/learninglens/lang/en/local_learninglens.php b/team_a/MoodlePlugin/learninglens/lang/en/local_learninglens.php
deleted file mode 100644
index 05c1c472..00000000
--- a/team_a/MoodlePlugin/learninglens/lang/en/local_learninglens.php
+++ /dev/null
@@ -1,23 +0,0 @@
-version = 2025031414; // YYYYMMDDHH (Year Month Day 24-hr time).
-$plugin->requires = 2022112800; // YYYYMMDDHH (The release version of Moodle 4.1).
-$plugin->component = 'local_learninglens'; // Name of your plugin (used for diagnostics).
-$plugin->maturity = MATURITY_ALPHA;
-?>
\ No newline at end of file
diff --git a/team_a/MoodlePlugin/local_adminer_moodle45_2025021700.zip b/team_a/MoodlePlugin/local_adminer_moodle45_2025021700.zip
deleted file mode 100644
index 63ab56c5..00000000
Binary files a/team_a/MoodlePlugin/local_adminer_moodle45_2025021700.zip and /dev/null differ
diff --git a/team_a/README.md b/team_a/README.md
deleted file mode 100644
index 28196599..00000000
--- a/team_a/README.md
+++ /dev/null
@@ -1,14 +0,0 @@
-# spring2025
-Fall SWEN 670 Cohort
-
-Team A (Learning Lens) - LLM Integrated Software Application for K-12 Education
-
-
-Members:
-Kevin Watts (Team Lead, Technical Writer)
-Dusty McKinnon (Technical Writer, Lead QA Engineer)
-Derek Sappington (Business Analyst, Software Engineer)
-Daniel Diep (Software Engineer / Programmer, Testing)
-Andrew Hammes (Business Analyst, Software Developer)
-Dinesh Ghimire (Software Developer, Tester)
-Nathaniel Boyd (Test Server Operator, System Admin)
diff --git a/team_a/pubspec.yaml b/team_a/pubspec.yaml
deleted file mode 100644
index 72ae07f9..00000000
--- a/team_a/pubspec.yaml
+++ /dev/null
@@ -1,31 +0,0 @@
-name: learninglens_app
-description: A new Flutter project.
-
-publish_to: 'none' # Remove this line if you wish to publish to pub.dev
-
-version: 0.0.1+1
-
-environment:
- sdk: ^3.1.1
-
-dependencies:
- flutter:
- sdk: flutter
- flutter_dotenv: ^5.1.0
- xml: ^6.2.2
- http: ^1.2.1
- flutter_quill: ^10.8.0
-
- english_words: ^4.0.0
- provider: ^6.0.0
- shared_preferences: ^2.2.0
-
-
-dev_dependencies:
- flutter_test:
- sdk: flutter
-
- flutter_lints: ^2.0.0
-
-flutter:
- uses-material-design: true
\ No newline at end of file
diff --git a/team_a/teamA/.example.env b/team_a/teamA/.example.env
deleted file mode 100644
index 06c3adc5..00000000
--- a/team_a/teamA/.example.env
+++ /dev/null
@@ -1,11 +0,0 @@
-# This file contains the required environment variables for the application.
-# To configure your environment, create a new `.env` file in the project root,
-# copy these variables, and populate them with the appropriate values.
-
-openai_apikey=
-MOODLE_USERNAME=
-MOODLE_PASSWORD=
-MOODLE_URL=
-claudeApiKey=
-perplexity_apikey=
-GOOGLE_CLIENT_ID=
\ No newline at end of file
diff --git a/team_a/teamA/.gitignore b/team_a/teamA/.gitignore
deleted file mode 100644
index 3af0d322..00000000
--- a/team_a/teamA/.gitignore
+++ /dev/null
@@ -1,51 +0,0 @@
-# Miscellaneous
-*.class
-*.log
-*.pyc
-*.swp
-.DS_Store
-.atom/
-.buildlog/
-.history
-.svn/
-migrate_working_dir/
-
-# IntelliJ related
-*.iml
-*.ipr
-*.iws
-.idea/
-
-# The .vscode folder contains launch configuration and tasks you configure in
-# VS Code which you may wish to be included in version control, so this line
-# is commented out by default.
-#.vscode/
-
-# .metadata file from flutter create
-.metadata
-
-# widget_test.dart file from flutter create
-test/widget_test.dart
-
-# Flutter/Dart/Pub related
-**/doc/api/
-**/ios/Flutter/.last_build_id
-.dart_tool/
-.flutter-plugins
-.flutter-plugins-dependencies
-.pub-cache/
-.pub/
-/build/
-
-/assets/api-keys.json
-
-# Symbolication related
-app.*.symbols
-
-# Obfuscation related
-app.*.map.json
-macos/*
-# Android Studio will place build artifacts here
-/android/app/debug
-/android/app/profile
-/android/app/release
diff --git a/team_a/teamA/.gitkeep b/team_a/teamA/.gitkeep
deleted file mode 100644
index e69de29b..00000000
diff --git a/team_a/teamA/README.md b/team_a/teamA/README.md
deleted file mode 100644
index a35fcbf3..00000000
--- a/team_a/teamA/README.md
+++ /dev/null
@@ -1,18 +0,0 @@
-#learninglens_app
-
-A new Flutter project.
-
-## Getting Started
-
-This project is a starting point for a Flutter application.
-
-A few resources to get you started if this is your first Flutter project:
-
-- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
-- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
-
-For help getting started with Flutter development, view the
-[online documentation](https://docs.flutter.dev/), which offers tutorials,
-samples, guidance on mobile development, and a full API reference.
-
-Test
\ No newline at end of file
diff --git a/team_a/teamA/analysis_options.yaml b/team_a/teamA/analysis_options.yaml
deleted file mode 100644
index fd6dbde2..00000000
--- a/team_a/teamA/analysis_options.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
-analyzer:
- errors:
- deprecated_member_use: ignore
-include: package:flutter_lints/flutter.yaml
-
-linter:
- rules:
- avoid_print: false
- prefer_const_constructors_in_immutables: false
- prefer_const_constructors: false
- prefer_const_literals_to_create_immutables: false
- prefer_final_fields: false
- unnecessary_breaks: true
- use_key_in_widget_constructors: false
diff --git a/team_a/teamA/assets/criteria.json b/team_a/teamA/assets/criteria.json
deleted file mode 100644
index b2a4a34a..00000000
--- a/team_a/teamA/assets/criteria.json
+++ /dev/null
@@ -1,84 +0,0 @@
-{
- "Criteria": [
- {
- "Name": "Introduction Effectiveness",
- "Description": "clarity and effectiveness in the introductory paragraph by presenting the essay's main argument or focus."
- },
- {
- "Name": "Thesis Statement Strength",
- "Description": "precision, clarity, and relevance in the thesis statement, effectively guiding the essay."
- },
- {
- "Name": "Conclusion Quality",
- "Description": "effectiveness in the conclusion by summarizing key points and reinforcing the thesis."
- },
- {
- "Name": "Depth of Analysis",
- "Description": "depth and complexity in analyzing the topic or themes within the essay."
- },
- {
- "Name": "Understanding of Context",
- "Description": "understanding of the historical, cultural, or social context related to the essay's topic."
- },
- {
- "Name": "Logical Flow",
- "Description": "logical progression of ideas and arguments throughout the essay."
- },
- {
- "Name": "Paragraph Structure",
- "Description": "coherence and clarity in individual paragraphs, with clear topic sentences and supporting details."
- },
- {
- "Name": "Transition Between Ideas",
- "Description": "effectiveness in transitioning between ideas or paragraphs within the essay."
- },
- {
- "Name": "Originality of Thought",
- "Description": "originality and creativity in their analysis or argument throughout the essay."
- },
- {
- "Name": "Use of Literary Devices",
- "Description": "identification and discussion of literary devices such as symbolism, metaphors, or imagery."
- },
- {
- "Name": "Grammar and Syntax",
- "Description": "use of grammar, punctuation, and sentence structure throughout the essay."
- },
- {
- "Name": "Adherence to Assignment Guidelines",
- "Description": "adherence to the assignment's requirements, including length, topic, and formatting."
- },
- {
- "Name": "Cohesion of Argument",
- "Description": "strength and consistency in the main argument across all sections of the essay."
- },
- {
- "Name": "Relevance of Evidence",
- "Description": "relevance and appropriateness in the supporting evidence used to back up claims."
- },
- {
- "Name": "Critical Thinking",
- "Description": "critical thinking skills in evaluating texts, arguments, or perspectives."
- },
- {
- "Name": "Depth of Research",
- "Description": "quality and depth of research, using credible sources where applicable."
- },
- {
- "Name": "Word Choice",
- "Description": "precision, sophistication, and appropriateness in their vocabulary use throughout the essay."
- },
- {
- "Name": "Persuasiveness",
- "Description": "persuasiveness in convincing the reader of their argument or perspective."
- },
- {
- "Name": "Citation and Referencing",
- "Description": "use of citation formats and accuracy in referencing sources (APA, MLA, etc.)."
- },
- {
- "Name": "Engagement with Counterarguments",
- "Description": "ability to acknowledge, engage with, and refute opposing viewpoints or counterarguments."
- }
- ]
-}
\ No newline at end of file
diff --git a/team_a/teamA/assets/login_image.png b/team_a/teamA/assets/login_image.png
deleted file mode 100644
index b56d6551..00000000
Binary files a/team_a/teamA/assets/login_image.png and /dev/null differ
diff --git a/team_a/teamA/devtools_options.yaml b/team_a/teamA/devtools_options.yaml
deleted file mode 100644
index fa0b357c..00000000
--- a/team_a/teamA/devtools_options.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-description: This file stores settings for Dart & Flutter DevTools.
-documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
-extensions:
diff --git a/team_a/teamA/lib/Api/experimental/README.md b/team_a/teamA/lib/Api/experimental/README.md
deleted file mode 100644
index 47b7adce..00000000
--- a/team_a/teamA/lib/Api/experimental/README.md
+++ /dev/null
@@ -1 +0,0 @@
-This folder is for experimental ideas only—not for production. It’s a proof of concept showing how an AI assistant can fetch info like students, courses, and quizzes from our app (Moodle-based). Think of it as a sandbox.
\ No newline at end of file
diff --git a/team_a/teamA/lib/Api/experimental/assistant/textbased_function_caller.dart b/team_a/teamA/lib/Api/experimental/assistant/textbased_function_caller.dart
deleted file mode 100644
index 874b1102..00000000
--- a/team_a/teamA/lib/Api/experimental/assistant/textbased_function_caller.dart
+++ /dev/null
@@ -1,109 +0,0 @@
-import 'package:learninglens_app/Api/lms/lms_interface.dart';
-import 'package:learninglens_app/beans/course.dart';
-import 'package:learninglens_app/beans/participant.dart';
-import 'package:learninglens_app/beans/quiz.dart';
-
-class TextBasedFunctionCaller {
- final LmsInterface lmsService;
-
- TextBasedFunctionCaller(this.lmsService);
-
- /// Dispatch the function call based on [functionName] and [args].
- Future callFunctionByName(
- String functionName, Map args) async {
- print('${functionName}, ${args}');
- try {
- switch (functionName) {
- case "getUserCourses":
- final courses = await lmsService.getUserCourses();
- return _formatCourses(courses);
-
- case "getQuizzes":
- if (!args.containsKey("courseID")) {
- return "Error: Missing 'courseID'.";
- }
- // Try parse courseID
- final courseID = int.tryParse(args["courseID"]);
- if (courseID == null) {
- return "Error: 'courseID' must be an integer.";
- }
-
- // Check if quizTopicId was provided
- if (!args.containsKey("quizTopicId") ||
- args["quizTopicId"] == null ||
- args["quizTopicId"].isEmpty) {
- // Not provided => pass null to getQuizzes
- final quizzes = await lmsService.getQuizzes(courseID, topicId: null);
- return _formatQuizzes(quizzes);
- } else {
- // Provided => parse it
- final int? quizTopicId = int.tryParse(args["quizTopicId"]);
- if (quizTopicId == null) {
- return "Error: 'quizTopicId' must be an integer if provided.";
- }
- final quizzes =
- await lmsService.getQuizzes(courseID, topicId: quizTopicId);
- return _formatQuizzes(quizzes);
- }
-
- case "getQuizGradesForParticipants":
- if (!args.containsKey("courseId") || !args.containsKey("quizId")) {
- return "Error: Missing courseId or quizId.";
- }
- final courseId = args["courseId"];
- final quizId = int.parse(args["quizId"]);
- final participants =
- await lmsService.getQuizGradesForParticipants(courseId, quizId);
- return _formatGrades(participants);
-
- case "getCourseParticipants":
- if (!args.containsKey("courseId")) {
- return "Error: Missing courseId.";
- }
- final courseId = args["courseId"];
- final participants = await lmsService.getCourseParticipants(courseId);
- return _formatParticipants(participants);
-
- default:
- return "Error: Unknown function '$functionName'.";
- }
- } catch (e) {
- // For your real app, handle or log the exception in detail
- return "Error: Exception occurred calling '$functionName': $e";
- }
- }
-
- // Helper: format a list of courses
- String _formatCourses(List courses) {
- if (courses.isEmpty) return "No enrolled courses found.";
- return courses
- .map((c) =>
- "Course: ${c.fullName} (ID: ${c.id}, quizTopicId: ${c.quizTopicId})")
- .join("\n");
- }
-
- // Helper: format a list of quizzes
- String _formatQuizzes(List quizzes) {
- if (quizzes.isEmpty) return "No quizzes found.";
- return quizzes.map((q) => "Quiz: ${q.name} (ID: ${q.id})").join("\n");
- }
-
- // Helper: format quiz grades for participants
- String _formatGrades(List participants) {
- if (participants.isEmpty) return "No participants or grades found.";
- return participants.map((p) {
- final gradeStr =
- (p.avgGrade != null) ? p.avgGrade.toString() : "No grade";
- return "Student: ${p.fullname} (ID: ${p.id}), Grade: $gradeStr";
- }).join("\n");
- }
-
- // Helper: format participants list
- String _formatParticipants(List participants) {
- if (participants.isEmpty) return "No participants found.";
- return participants
- .map((p) =>
- "Student: ${p.fullname} (ID: ${p.id}) Roles: ${p.roles.join(', ')}")
- .join("\n");
- }
-}
\ No newline at end of file
diff --git a/team_a/teamA/lib/Api/experimental/assistant/textbased_function_caller_view.dart b/team_a/teamA/lib/Api/experimental/assistant/textbased_function_caller_view.dart
deleted file mode 100644
index e2cb08c5..00000000
--- a/team_a/teamA/lib/Api/experimental/assistant/textbased_function_caller_view.dart
+++ /dev/null
@@ -1,218 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:learninglens_app/Api/experimental/assistant/textbased_function_caller.dart';
-import 'package:learninglens_app/Api/experimental/assistant/textbased_llm_client.dart';
-import 'package:learninglens_app/Api/llm/enum/llm_enum.dart';
-import 'package:learninglens_app/Api/llm/grok_api.dart';
-import 'package:learninglens_app/Api/llm/llm_api_modules_base.dart';
-import 'package:learninglens_app/Api/llm/openai_api.dart';
-import 'package:learninglens_app/Api/llm/perplexity_api.dart';
-import 'package:learninglens_app/Api/lms/factory/lms_factory.dart';
-import 'package:learninglens_app/Controller/custom_appbar.dart';
-import 'package:learninglens_app/services/local_storage_service.dart';
-
-class TextBasedFunctionCallerView extends StatefulWidget {
- const TextBasedFunctionCallerView({Key? key}) : super(key: key);
-
- @override
- _TextBasedFunctionCallerViewState createState() =>
- _TextBasedFunctionCallerViewState();
-}
-
-class _TextBasedFunctionCallerViewState
- extends State {
- final TextEditingController _controller = TextEditingController();
- final FocusNode _focusNode = FocusNode();
- final List> _messages = [];
- late TextBasedLLMClient _chatGPT;
- bool _isThinking = false; // Tracks if the bot is thinking
-
- LlmType selectedLLM = LlmType.GROK; //default to chatgpt
- late LLM llm;
-
- @override
- void initState() {
- super.initState();
-
- llm = getLLM();
-
- _chatGPT = TextBasedLLMClient(
- llm,
- TextBasedFunctionCaller(LmsFactory.getLmsService()),
- );
- }
-
- // grabs the selected LLM.
- LLM getLLM() {
- final aiModel;
- if (selectedLLM == LlmType.CHATGPT) {
- aiModel = OpenAiLLM(LocalStorageService.getOpenAIKey());
- } else if (selectedLLM == LlmType.GROK) {
- aiModel = GrokLLM(LocalStorageService.getGrokKey());
- } else if (selectedLLM == LlmType.PERPLEXITY) {
- // aiModel = OpenAiLLM(perplexityApiKey);
- aiModel = PerplexityLLM(LocalStorageService.getPerplexityKey());
- } else {
- // default
- aiModel = OpenAiLLM(LocalStorageService.getOpenAIKey());
- }
- return aiModel;
- }
-
- void _sendMessage() async {
- if (_controller.text.isEmpty) return;
-
- setState(() {
- _messages.add({"sender": "user", "text": _controller.text});
- _isThinking = true; // Show thinking dots
- });
-
- final userMessage = _controller.text;
- _controller.clear();
-
- // Add a "thinking" message
- // setState(() {
- // _messages.add({"sender": "bot", "text": "..."});
- // });
-
- String response = await _chatGPT.sendMessage(userMessage);
-
- setState(() {
- // _messages.removeLast(); // Remove "thinking" message
- _messages.add({"sender": "bot", "text": response});
- _isThinking = false; // Bot has finished responding
- });
-
- _focusNode.requestFocus(); // Refocus input field
- }
-
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- appBar: CustomAppBar(
- title: 'EduLense Assistant (Beta textbased)',
- userprofileurl: LmsFactory.getLmsService().profileImage ?? '',
- ),
- body: Column(
- children: [
- Expanded(
- child: ListView.builder(
- itemCount: _messages.length,
- itemBuilder: (context, index) {
- final message = _messages[index];
- bool isUser = message["sender"] == "user";
-
- return Padding(
- padding: EdgeInsets.symmetric(vertical: 5, horizontal: 10),
- child: Row(
- mainAxisAlignment: isUser
- ? MainAxisAlignment.end
- : MainAxisAlignment.start,
- children: [
- if (!isUser) ...[
- CircleAvatar(
- backgroundColor: Colors.grey[200],
- child: Icon(Icons.android, color: Colors.blueAccent),
- ),
- SizedBox(width: 8),
- ],
- Flexible(
- child: Container(
- padding: EdgeInsets.all(12),
- decoration: BoxDecoration(
- color:
- isUser ? Colors.blueAccent : Colors.grey[300],
- borderRadius: BorderRadius.circular(15),
- ),
- child: Text(
- message["text"]!,
- style: TextStyle(
- color: isUser ? Colors.white : Colors.black,
- fontSize: 16,
- ),
- ),
- ),
- ),
- if (isUser) ...[
- SizedBox(width: 8),
- CircleAvatar(
- backgroundColor: Colors.grey[200],
- child: Icon(Icons.person, color: Colors.blueAccent),
- ),
- ],
- ],
- ),
- );
- },
- ),
- ),
- if (_isThinking)
- Padding(
- padding: const EdgeInsets.all(8.0),
- child: CircularProgressIndicator(), // Loading indicator
- ),
- Padding(
- padding: const EdgeInsets.all(10.0),
- child: Row(
- children: [
- Expanded(
- child: TextField(
- controller: _controller,
- focusNode: _focusNode,
- decoration: InputDecoration(
- hintText: "Ask ${selectedLLM.displayName}...",
- border: OutlineInputBorder(
- borderRadius: BorderRadius.circular(20),
- ),
- ),
- onSubmitted: (value) => _sendMessage(),
- ),
- ),
- SizedBox(width: 8),
- ClipOval(
- child: Material(
- color: Colors.blueAccent,
- child: InkWell(
- onTap: _sendMessage,
- child: Padding(
- padding: const EdgeInsets.all(10.0),
- child: Icon(Icons.send, color: Colors.white),
- ),
- ),
- ),
- ),
- DropdownButton(
- value: selectedLLM,
- onChanged: (LlmType? newValue) {
- setState(() {
- selectedLLM = newValue!;
- llm = getLLM();
-
- _chatGPT = TextBasedLLMClient(
- llm,
- TextBasedFunctionCaller(LmsFactory.getLmsService()),
- );
-
- });
- },
- items: LlmType.values.map((LlmType llm) {
- return DropdownMenuItem(
- value: llm,
- enabled: LocalStorageService.userHasLlmKey(llm),
- child: Text(
- llm.displayName,
- style: TextStyle(
- color: LocalStorageService.userHasLlmKey(llm)
- ? Colors.black87
- : Colors.grey,
- ),
- ),
- );
- }).toList()),
- ],
- ),
- ),
- ],
- ),
- );
- }
-}
diff --git a/team_a/teamA/lib/Api/experimental/assistant/textbased_llm_client.dart b/team_a/teamA/lib/Api/experimental/assistant/textbased_llm_client.dart
deleted file mode 100644
index 6e781877..00000000
--- a/team_a/teamA/lib/Api/experimental/assistant/textbased_llm_client.dart
+++ /dev/null
@@ -1,135 +0,0 @@
-import 'dart:convert';
-import 'package:http/http.dart' as http;
-import 'package:learninglens_app/Api/experimental/assistant/textbased_function_caller.dart';
-import 'package:learninglens_app/Api/llm/llm_api_modules_base.dart';
-import 'package:learninglens_app/Api/llm/prompt_engine.dart';
-import 'package:learninglens_app/services/api_service.dart';
-
-// Replicate the functionality used in chatgpt_client, but swap over to prompt engineering instead of the function caller.
-// This code gears the development for the assistant to be more generic in terms of which llm is used rather than relying on
-// functionality developement by OpenAI.
-class TextBasedLLMClient {
- final LLM llm;
- final TextBasedFunctionCaller functionCaller;
-
- /// Maintains the entire conversation (system, user, assistant).
- final List> _conversation = [];
-
- TextBasedLLMClient(this.llm, this.functionCaller) {
- // Add system instructions once at the beginning
- _conversation.add({
- "role": "system",
- "content": PromptEngine.prompt_assistant
- });
- }
-
- /// Send a user message (multi-turn). If the LLM keeps calling functions,
- /// we'll keep looping until we get a plain text answer.
- Future sendMessage(String userMessage) async {
- // 1) Add the user's message to conversation
- _conversation.add({
- "role": "user",
- "content": userMessage,
- });
-
- // 2) Multi-turn loop: keep calling the LLM until we get a non-CALL response
- while (true) {
- final llmReply = await _callLLM(_conversation);
- if (llmReply == null || llmReply.isEmpty) {
- return "No response from the LLM.";
- }
-
- final trimmedReply = llmReply.trim();
-
- // If the model requests a function call
- if (trimmedReply.startsWith("CALL ")) {
- // Handle the function call and add the result to the conversation
- final functionResult = await _handleFunctionCall(trimmedReply);
-
- // Insert the function result as an "assistant" message
- _conversation.add({
- "role": "assistant",
- "content": functionResult,
- });
-
- // Then loop again so the LLM can see that result and possibly call another function
- } else {
- // It's a final textual answer
- _conversation.add({
- "role": "assistant",
- "content": trimmedReply,
- });
- return trimmedReply;
- }
- }
- }
-
- /// Calls the OpenAI Chat Completion API with the entire conversation so far
- Future _callLLM(List> conversation) async {
-
-
- final response = await ApiService().httpPost(
- Uri.parse(llm.url),
- headers: {
- "Authorization": 'Bearer ${llm.apiKey}',
- "Content-Type": "application/json",
- },
- body: jsonEncode({
- "model": llm.model,
- "messages": conversation.map((m) {
- return {"role": m["role"], "content": m["content"]};
- }).toList(),
- "temperature": 0.7,
- "top_p": 0.9,
- }),
- );
-
- if (response.statusCode != 200) {
- return "Error from LLM: ${response.statusCode} => ${response.body}";
- }
-
- final jsonData = jsonDecode(response.body);
- if (jsonData["choices"] == null || jsonData["choices"].isEmpty) {
- return null;
- }
-
- return jsonData["choices"][0]["message"]["content"];
- }
-
- /// Parses a "CALL functionName(...)" string, calls the local function, returns its result
- Future _handleFunctionCall(String callString) async {
- // Example: "CALL getQuizzes(courseID=101, quizTopicId=201)"
- final afterCall = callString.substring(5).trim(); // remove "CALL "
- final openParenIndex = afterCall.indexOf("(");
- if (openParenIndex == -1) {
- return "Error: malformed function call, missing '('";
- }
-
- final functionName = afterCall.substring(0, openParenIndex).trim();
-
- final closeParenIndex = afterCall.indexOf(")", openParenIndex);
- if (closeParenIndex == -1) {
- return "Error: malformed function call, missing ')'";
- }
-
- final argsString = afterCall.substring(openParenIndex + 1, closeParenIndex).trim();
-
- // Parse key=value pairs
- final Map argsMap = {};
- if (argsString.isNotEmpty) {
- final pairs = argsString.split(",");
- for (final pair in pairs) {
- final parts = pair.split("=");
- if (parts.length == 2) {
- final key = parts[0].trim();
- final val = parts[1].trim();
- argsMap[key] = val; // Keep as string; parse to int if you like
- }
- }
- }
-
- // Actually call the function
- final result = await functionCaller.callFunctionByName(functionName, argsMap);
- return result;
- }
-}
diff --git a/team_a/teamA/lib/Api/llm/claudeai_api.dart b/team_a/teamA/lib/Api/llm/claudeai_api.dart
deleted file mode 100644
index c93714b7..00000000
--- a/team_a/teamA/lib/Api/llm/claudeai_api.dart
+++ /dev/null
@@ -1,106 +0,0 @@
-import 'dart:async';
-import 'dart:convert';
-import 'package:learninglens_app/services/api_service.dart';
-
-class ClaudeAiAPI {
- final String claudeAiKey;
- ClaudeAiAPI(this.claudeAiKey);
-
- Map convertHttpRespToJson(String httpResponseString) {
- return (json.decode(httpResponseString) as Map);
- }
-
- String getPostBody(String queryMessage) {
- return jsonEncode({
- 'model': 'claude-3-5-sonnet-20240620',
- 'max_tokens': 1024,
- 'system': 'Be precise and concise',
- 'messages': [
- {'role': 'user', 'content': queryMessage}
- ]
- });
- }
-
- Map getPostHeaders() {
- return ({
- 'anthropic-version': '2023-06-01',
- 'content-type': 'application/json',
- 'anthropic-dangerous-direct-browser-access': 'true',
- 'x-api-key': claudeAiKey,
- });
- }
-
- Uri getPostUrl() => Uri.https('api.anthropic.com', '/v1/messages');
-
- Future postMessage(
- Uri url, Map postHeaders, Object postBody) async {
- final httpPackageResponse =
- await ApiService().httpPost(url, headers: postHeaders, body: postBody);
-
- if (httpPackageResponse.statusCode != 200) {
- print('Failed to retrieve the http package!');
- print('statusCode : ${httpPackageResponse.statusCode}');
- print('Reason: ${httpPackageResponse.body}');
-
- return "";
- }
-
- return httpPackageResponse.body;
- }
-
- List parseQueryResponse(String resp) {
- // ignore: prefer_adjacent_string_concatenation
- String quizRegExp =
- // r'(<\?xml.*?\?>\s*(\s*.*?\s*.*?\s*(.*?) \s*.*?(\s*.*?\s*(.*?) )+\s* \s*.*?\s*(.*?) \s*.*? )+\s* )';
- r'(<\?xml.*?\?>\s*.*? )';
-
- RegExp exp = RegExp(quizRegExp);
- String respNoNewlines = resp.replaceAll('\n', '');
- Iterable matches = exp.allMatches(respNoNewlines);
- List parsedResp = [];
-
- print("Parsing the query response - matches: $matches");
-
- for (final m in matches) {
- if (m.group(0) != null) {
- parsedResp.add(m.group(0)!);
-
- print("This is a match : ${m.group(0)}");
- print("Number of groups in the match: ${m.groupCount}");
- print("parsedResp : $parsedResp");
- }
- }
-
- return parsedResp;
- }
-
- Future postToLlm(String queryPrompt) async {
- var resp = "";
-
- // use the following test query so Perplexity doesn't charge
- // 'How many stars are there in our galaxy?'
- if (queryPrompt.isNotEmpty) {
- resp = await queryAI(queryPrompt);
- }
- return resp;
- }
-
- Future queryAI(String query) async {
- final postHeaders = getPostHeaders();
- final postBody = getPostBody(query);
- final httpPackageUrl = getPostUrl();
-
- final httpPackageRespString =
- await postMessage(httpPackageUrl, postHeaders, postBody);
-
- final httpPackageResponseJson =
- convertHttpRespToJson(httpPackageRespString);
-
- var retResponse = "";
- for (var respChoice in httpPackageResponseJson['content']) {
- retResponse += respChoice['text'];
- }
- // print("In queryAI - content : $retResponse");
- return retResponse;
- }
-}
\ No newline at end of file
diff --git a/team_a/teamA/lib/Api/llm/enum/llm_enum.dart b/team_a/teamA/lib/Api/llm/enum/llm_enum.dart
deleted file mode 100644
index fc48cf81..00000000
--- a/team_a/teamA/lib/Api/llm/enum/llm_enum.dart
+++ /dev/null
@@ -1,8 +0,0 @@
-enum LlmType {
- CHATGPT('ChatGPT'),
- PERPLEXITY('Perplexity'),
- GROK('Grok');
-
- final String displayName;
- const LlmType(this.displayName);
-}
\ No newline at end of file
diff --git a/team_a/teamA/lib/Api/llm/grok_api.dart b/team_a/teamA/lib/Api/llm/grok_api.dart
deleted file mode 100644
index f197b933..00000000
--- a/team_a/teamA/lib/Api/llm/grok_api.dart
+++ /dev/null
@@ -1,192 +0,0 @@
-import 'dart:async';
-import 'dart:convert';
-import 'package:http/http.dart' as http;
-import 'package:learninglens_app/Api/llm/llm_api_modules_base.dart';
-import 'package:learninglens_app/services/api_service.dart';
-
-class GrokLLM implements LLM {
- @override
- final String apiKey;
- @override
- final String url = 'https://api.x.ai/v1/chat/completions';
- @override
- final String model = 'grok-2-latest';
-
- GrokLLM(this.apiKey);
-
- Map convertHttpRespToJson(String httpResponseString) {
- return (json.decode(httpResponseString) as Map);
- }
-
- ///
- ///
- ///
- String getPostBody(String queryMessage) {
- return jsonEncode({
- 'model': model,
- 'messages': [
- {'role': 'system', 'content': 'Be precise and concise'},
- {'role': 'user', 'content': queryMessage}
- ]
- });
- }
-
- ///
- ///
- ///
- Map getPostHeaders() {
- return ({
- 'accept': 'application/json',
- 'Content-Type': 'application/json',
- 'Authorization': 'Bearer $apiKey',
- });
- }
-
- ///
- ///
- ///
- Uri getPostUrl() => Uri.https('api.x.ai', '/v1/chat/completions');
-
- ///
- ///
- ///
- Future postMessage(
- Uri url, Map postHeaders, Object postBody) async {
- final httpPackageResponse =
- await ApiService().httpPost(url, headers: postHeaders, body: postBody);
-
- print(url);
- print(postHeaders);
- print(postBody);
-
- if (httpPackageResponse.statusCode != 200) {
- print('Failed to retrieve the http package!');
- print('statusCode : ${httpPackageResponse.statusCode}');
- print('Reason: ${httpPackageResponse.body}');
-
- return "";
- }
-
- print("In postmessage : ${httpPackageResponse.body}");
- return httpPackageResponse.body;
- }
-
- List parseQueryResponse(String resp) {
- // ignore: prefer_adjacent_string_concatenation
- String quizRegExp =
- // r'(<\?xml.*?\?>\s*(\s*.*?\s*.*?\s*(.*?) \s*.*?(\s*.*?\s*(.*?) )+\s* \s*.*?\s*(.*?) \s*.*? )+\s* )';
- r'(<\?xml.*?\?>\s*.*? )';
-
- RegExp exp = RegExp(quizRegExp);
- String respNoNewlines = resp.replaceAll('\n', '');
- Iterable matches = exp.allMatches(respNoNewlines);
- List parsedResp = [];
-
- print("Parsing the query response - matches: $matches");
-
- for (final m in matches) {
- if (m.group(0) != null) {
- parsedResp.add(m.group(0)!);
-
- print("This is a match : ${m.group(0)}");
- print("Number of groups in the match: ${m.groupCount}");
- print("parsedResp : $parsedResp");
- }
- }
-
- return parsedResp;
- }
-
- ///
- ///
- ///
- Future postToLlm(String queryPrompt) async {
- var resp = "";
-
- // use the following test query so Perplexity doesn't charge
- // 'How many stars are there in our galaxy?'
- if (queryPrompt.isNotEmpty) {
- resp = await queryAI(queryPrompt);
- }
- return resp;
- }
-
- ///
- ///
- ///
- Future queryAI(String query) async {
- final postHeaders = getPostHeaders();
- final postBody = getPostBody(query);
- final httpPackageUrl = getPostUrl();
-
- final httpPackageRespString =
- await postMessage(httpPackageUrl, postHeaders, postBody);
-
- final httpPackageResponseJson =
- convertHttpRespToJson(httpPackageRespString);
-
- var retResponse = "";
- for (var respChoice in httpPackageResponseJson['choices']) {
- retResponse += respChoice['message']['content'];
- }
- print("In queryAI - content : $retResponse");
- return retResponse;
- }
-
- Future getChatResponse(String prompt) async {
-
- final postHeaders = getPostHeaders();
- final postBody = getPostBody(prompt);
- final httpPackageUrl = getPostUrl();
-
- try {
- // Make the POST request to the chat completions endpoint
- var response = await ApiService().httpPost(httpPackageUrl, headers: postHeaders, body: postBody);
-
- // Check for successful response
- if (response.statusCode == 200) {
- var data = jsonDecode(response.body);
- return data['choices'][0]['message']['content']
- .trim(); // Return the chat response
- } else {
- // Log the error response and handle failure cases
- print('Failed to fetch response. Status code: ${response.statusCode}');
- print('Response body: ${response.body}');
- return 'Sorry, I couldn’t fetch a response. Please try again.';
- }
- } catch (error) {
- // Log and handle connection or parsing errors
- print('Error occurred: $error');
- return 'An error occurred. Please check your internet connection and try again.';
- }
- }
-
-
-
-
- @override
- Future generate(String prompt) async {
- final url = Uri.parse(this.url); // Hypothetical endpoint
- final headers = {
- 'Authorization': 'Bearer $apiKey',
- 'Content-Type': 'application/json',
- };
- final body = jsonEncode({
- 'model': model, // Use the configurable model
- 'messages': [
- {'role': 'user', 'content': prompt},
- ],
- 'max_tokens': 500, // Limit response length
- });
-
- final response = await http.post(url, headers: headers, body: body);
- if (response.statusCode != 200) {
- throw Exception('Grok API error: ${response.statusCode} - ${response.body}');
- }
-
- final data = jsonDecode(response.body);
- return data['choices'][0]['message']['content'].trim(); // Adjust based on actual response structure
- }
-
-
-}
\ No newline at end of file
diff --git a/team_a/teamA/lib/Api/llm/llm_api_modules_base.dart b/team_a/teamA/lib/Api/llm/llm_api_modules_base.dart
deleted file mode 100644
index bbb6bdf9..00000000
--- a/team_a/teamA/lib/Api/llm/llm_api_modules_base.dart
+++ /dev/null
@@ -1,17 +0,0 @@
-// TODO: Put public facing types in this file.
-
-/// Checks if you are awesome. Spoiler: you are.
-class Awesome {
- bool get isAwesome => true;
-}
-//This is the abstract class for the LLM API module
-//It has a single method called generate which takes a string prompt and returns a string
-abstract class LLM {
- final String apiKey;
- String get url;
- String get model;
-
- LLM(this.apiKey);
-
- Future generate(String prompt);
-}
diff --git a/team_a/teamA/lib/Api/llm/openai_api.dart b/team_a/teamA/lib/Api/llm/openai_api.dart
deleted file mode 100644
index e5e96b1c..00000000
--- a/team_a/teamA/lib/Api/llm/openai_api.dart
+++ /dev/null
@@ -1,191 +0,0 @@
-import 'dart:async';
-import 'dart:convert';
-import 'package:http/http.dart' as http;
-import 'package:learninglens_app/Api/llm/llm_api_modules_base.dart';
-import 'package:learninglens_app/services/api_service.dart';
-
-class OpenAiLLM implements LLM {
- @override
- final String apiKey;
- @override
- final String url = 'https://api.openai.com/v1/chat/completions';
- @override
- final String model = 'gpt-4o-mini';
- OpenAiLLM(this.apiKey);
-
- Map convertHttpRespToJson(String httpResponseString) {
- return (json.decode(httpResponseString) as Map);
- }
-
- ///
- ///
- ///
- String getPostBody(String queryMessage) {
- return jsonEncode({
- 'model': model,
- 'messages': [
- {'role': 'system', 'content': 'Be precise and concise'},
- {'role': 'user', 'content': queryMessage}
- ]
- });
- }
-
- ///
- ///
- ///
- Map getPostHeaders() {
- return ({
- 'accept': 'application/json',
- 'content-type': 'application/json',
- 'authorization': 'Bearer $apiKey',
- });
- }
-
- ///
- ///
- ///
- Uri getPostUrl() => Uri.https('api.openai.com', '/v1/chat/completions');
-
- ///
- ///
- ///
- Future postMessage(
- Uri url, Map postHeaders, Object postBody) async {
- final httpPackageResponse =
- await ApiService().httpPost(url, headers: postHeaders, body: postBody);
-
- print(url);
- print(postHeaders);
- print(postBody);
-
- if (httpPackageResponse.statusCode != 200) {
- print('Failed to retrieve the http package!');
- print('statusCode : ${httpPackageResponse.statusCode}');
- print('Reason: ${httpPackageResponse.body}');
-
- return "";
- }
-
- print("In postmessage : ${httpPackageResponse.body}");
- return httpPackageResponse.body;
- }
-
- List parseQueryResponse(String resp) {
- // ignore: prefer_adjacent_string_concatenation
- String quizRegExp =
- // r'(<\?xml.*?\?>\s*(\s*.*?\s*.*?\s*(.*?) \s*.*?(\s*.*?\s*(.*?) )+\s* \s*.*?\s*(.*?) \s*.*? )+\s* )';
- r'(<\?xml.*?\?>\s*.*? )';
-
- RegExp exp = RegExp(quizRegExp);
- String respNoNewlines = resp.replaceAll('\n', '');
- Iterable matches = exp.allMatches(respNoNewlines);
- List parsedResp = [];
-
- print("Parsing the query response - matches: $matches");
-
- for (final m in matches) {
- if (m.group(0) != null) {
- parsedResp.add(m.group(0)!);
-
- print("This is a match : ${m.group(0)}");
- print("Number of groups in the match: ${m.groupCount}");
- print("parsedResp : $parsedResp");
- }
- }
-
- return parsedResp;
- }
-
- ///
- ///
- ///
- Future postToLlm(String queryPrompt) async {
- var resp = "";
-
- // use the following test query so Perplexity doesn't charge
- // 'How many stars are there in our galaxy?'
- if (queryPrompt.isNotEmpty) {
- resp = await queryAI(queryPrompt);
- }
- return resp;
- }
-
- ///
- ///
- ///
- Future queryAI(String query) async {
- final postHeaders = getPostHeaders();
- final postBody = getPostBody(query);
- final httpPackageUrl = getPostUrl();
-
- final httpPackageRespString =
- await postMessage(httpPackageUrl, postHeaders, postBody);
-
- final httpPackageResponseJson =
- convertHttpRespToJson(httpPackageRespString);
-
- var retResponse = "";
- for (var respChoice in httpPackageResponseJson['choices']) {
- retResponse += respChoice['message']['content'];
- }
- print("In queryAI - content : $retResponse");
- return retResponse;
- }
-
- Future getChatResponse(String prompt) async {
-
- final postHeaders = getPostHeaders();
- final postBody = getPostBody(prompt);
- final httpPackageUrl = getPostUrl();
-
- try {
- // Make the POST request to the chat completions endpoint
- var response = await ApiService().httpPost(httpPackageUrl, headers: postHeaders, body: postBody);
-
- // Check for successful response
- if (response.statusCode == 200) {
- var data = jsonDecode(response.body);
- return data['choices'][0]['message']['content']
- .trim(); // Return the chat response
- } else {
- // Log the error response and handle failure cases
- print('Failed to fetch response. Status code: ${response.statusCode}');
- print('Response body: ${response.body}');
- return 'Sorry, I couldn’t fetch a response. Please try again.';
- }
- } catch (error) {
- // Log and handle connection or parsing errors
- print('Error occurred: $error');
- return 'An error occurred. Please check your internet connection and try again.';
- }
- }
-
- @override
- Future generate(String prompt) async {
- print("In generate - prompt : $prompt");
-
-final url = Uri.parse(this.url);
- final headers = {
- 'Authorization': 'Bearer $apiKey',
- 'Content-Type': 'application/json',
- };
- final body = jsonEncode({
- 'model': model,
- 'messages': [
- {'role': 'system', 'content': 'You are a helpful assistant.'},
- {'role': 'user', 'content': prompt},
- ],
- 'max_tokens': 500, // Limit response length
- });
-
- final response = await http.post(url, headers: headers, body: body);
- if (response.statusCode != 200) {
- throw Exception('OpenAI API error: ${response.statusCode} - ${response.body}');
- }
-
- final data = jsonDecode(response.body);
- return data['choices'][0]['message']['content'].trim();
-
- }
-
-}
\ No newline at end of file
diff --git a/team_a/teamA/lib/Api/llm/perplexity_api.dart b/team_a/teamA/lib/Api/llm/perplexity_api.dart
deleted file mode 100644
index 49aae50a..00000000
--- a/team_a/teamA/lib/Api/llm/perplexity_api.dart
+++ /dev/null
@@ -1,168 +0,0 @@
-import 'dart:async';
-import 'dart:convert';
-import 'package:learninglens_app/Api/llm/llm_api_modules_base.dart';
-import 'package:learninglens_app/services/api_service.dart';
-
-class PerplexityLLM implements LLM
-{
- @override
- final String apiKey;
- @override
- final String url = 'https://api.perplexity.ai/chat/completions';
- @override
- final String model = 'llama-3.1-sonar-large-128k-online';
-
- PerplexityLLM(this.apiKey);
-
- Map convertHttpRespToJson(String httpResponseString)
- {
- return (json.decode(httpResponseString) as Map);
- }
-
- //
- String getPostBody(String queryMessage)
- {
- return jsonEncode({
- // 'model': 'llama-3-sonar-large-32k-online',
- //'model': 'llama-3.1-sonar-large-128k-chat',
- 'model': model,
- 'messages': [
- {'role': 'system', 'content': 'Be precise and concise'},
- {'content': queryMessage, 'role': 'user'}
- ]
- });
- }
-
- //
- Map getPostHeaders()
- {
- return ({
- 'accept': 'application/json',
- 'content-type': 'application/json',
- 'authorization': 'Bearer $apiKey',
- });
- }
-
- //
- Uri getPostUrl() => Uri.https(this.url);
-
- //
- Future postMessage(
- Uri url, Map postHeaders, Object postBody) async
- {
- final httpPackageResponse =
- await ApiService().httpPost(url, headers: postHeaders, body: postBody);
-
- if (httpPackageResponse.statusCode != 200) {
- print('Failed to retrieve the http package!');
- print('statusCode : ${httpPackageResponse.statusCode}');
- print('body: ${httpPackageResponse.body}');
- return "";
- }
-
- print("In postmessage : ${httpPackageResponse.body}");
- return httpPackageResponse.body;
- }
-
- List parseQueryResponse(String resp)
- {
- // ignore: prefer_adjacent_string_concatenation
- String quizRegExp =
- // r'(<\?xml.*?\?>\s*(\s*.*?\s*.*?\s*(.*?) \s*.*?(\s*.*?\s*(.*?) )+\s* \s*.*?\s*(.*?) \s*.*? )+\s* )';
- r'(<\?xml.*?\?>\s*.*? )';
-
- RegExp exp = RegExp(quizRegExp);
- String respNoNewlines = resp.replaceAll('\n', '');
- Iterable matches = exp.allMatches(respNoNewlines);
- List parsedResp = [];
-
- print("Parsing the query response - matches: $matches");
-
- for (final m in matches) {
- if (m.group(0) != null) {
- parsedResp.add(m.group(0)!);
-
- print("This is a match : ${m.group(0)}");
- print("Number of groups in the match: ${m.groupCount}");
- print("parsedResp : $parsedResp");
- }
- }
-
- return parsedResp;
- }
-
- //
- Future postToLlm(String queryPrompt) async
- {
- var resp = "";
-
- // use the following test query so Perplexity doesn't charge
- // 'How many stars are there in our galaxy?'
- if (queryPrompt.isNotEmpty) {
- resp = await queryAI(queryPrompt);
- }
- return resp;
- }
-
- //
- Future queryAI(String query) async
- {
- final postHeaders = getPostHeaders();
- final postBody = getPostBody(query);
- final httpPackageUrl = getPostUrl();
-
- final httpPackageRespString =
- await postMessage(httpPackageUrl, postHeaders, postBody);
-
- final httpPackageResponseJson =
- convertHttpRespToJson(httpPackageRespString);
-
- var retResponse = "";
- for (var respChoice in httpPackageResponseJson['choices']) {
- retResponse += respChoice['message']['content'];
- }
- // print("In queryAI - content : $retResponse");
- return retResponse;
- }
-
- Future getChatResponse(String prompt) async {
-
- final postHeaders = getPostHeaders();
- final postBody = getPostBody(prompt);
- final httpPackageUrl = getPostUrl();
-
- try {
- // Make the POST request to the chat completions endpoint
- var response = await ApiService().httpPost(httpPackageUrl, headers: postHeaders, body: postBody);
-
- // Check for successful response
- if (response.statusCode == 200) {
- var data = jsonDecode(response.body);
- return data['choices'][0]['message']['content']
- .trim(); // Return the chat response
- } else {
- // Log the error response and handle failure cases
- print('Failed to fetch response. Status code: ${response.statusCode}');
- print('Response body: ${response.body}');
- return 'Sorry, I couldn’t fetch a response. Please try again.';
- }
- } catch (error) {
- // Log and handle connection or parsing errors
- print('Error occurred: $error');
- return 'An error occurred. Please check your internet connection and try again.';
- }
- }
-
- @override
- Future generate(String prompt) async {
- print('Generating response for prompt Perplexity: $prompt');
-
- final postHeaders = getPostHeaders();
- final postBody = getPostBody(prompt);
- final url = getPostUrl();
- final responseString = await postMessage(url, postHeaders, postBody);
- final responseJson = jsonDecode(responseString);
- return responseJson['choices'][0]['message']['content'].trim();
- }
-
-}
diff --git a/team_a/teamA/lib/Api/llm/prompt_engine.dart b/team_a/teamA/lib/Api/llm/prompt_engine.dart
deleted file mode 100644
index 95d338af..00000000
--- a/team_a/teamA/lib/Api/llm/prompt_engine.dart
+++ /dev/null
@@ -1,133 +0,0 @@
-import 'package:learninglens_app/beans/assignment_form.dart';
-
-class PromptEngine {
-
- static const prompt_quizgen_college =
- 'Generate a quiz in XML format '
- 'that is compatible with Moodle XML import. The quiz is to be on the subject of '
- '[subject] and should be related to [topic]. '
- 'The quiz should be the same level of difficulty for college [gradelevel] students of the '
- 'English-speaking language. ';
-
- static const prompt_quizgen_high_school =
- 'Generate a quiz in XML format '
- 'that is compatible with Moodle XML import. The quiz is to be on the subject of '
- '[subject] and should be related to [topic]. '
- 'The quiz should be the same level of difficulty for high school [gradelevel] students of the '
- 'English-speaking language. ';
-
- static const prompt_quizgen_other =
- 'Generate a quiz in XML format '
- 'that is compatible with Moodle XML import. The quiz is to be on the subject of '
- '[subject] and should be related to [topic]. '
- 'The quiz should be the same level of difficulty for [gradelevel] grade students of the '
- 'English-speaking language. ';
-
-
- static const prompt_quizgen_xmlonly =
- 'Provide only the XML in your response. ';
-
- static const prompt_quizgen_choice_example =
- 'Please use this XML sample as a template for your response: Question 1 What does CPU stand for? Central Processing Unit Correct! The CPU is the primary component of a computer that processes instructions. Computer Processing Unit Incorrect. While close, this is not the correct term. Central Program Unit Incorrect. This is not the correct term for CPU. Control Processing Unit Incorrect. While the CPU does involve processing, this is not the correct term. Question 2 Which of the following is not a programming paradigm? Object-Oriented Programming Incorrect. Object-Oriented Programming is a valid programming paradigm. Functional Programming Incorrect. Functional Programming is a valid programming paradigm. Quantum Programming Correct! While quantum computing exists, "Quantum Programming" is not a standard programming paradigm. Procedural Programming Incorrect. Procedural Programming is a valid programming paradigm. Question 3 What is the primary function of an operating system? Manage computer hardware and software resources Correct! The operating system acts as an intermediary between programs and the computer hardware. Create documents and spreadsheets Incorrect. This is the function of application software, not the operating system. Browse the internet Incorrect. Web browsing is a function of web browser applications, not the operating system itself. Play video games Incorrect. Playing games is a function of game software, not the primary function of an operating system. Question 4 Which of the following is an example of a high-level programming language? Assembly Incorrect. Assembly is a low-level programming language. Machine Code Incorrect. Machine code is the lowest-level programming language. Python Correct! Python is an example of a high-level programming language. Binary Incorrect. Binary is not a programming language, but a number system used in computing. Question 5 What does HTML stand for? Hypertext Markup Language Correct! HTML is used to structure content on the web. Hyperlinks and Text Markup Language Incorrect. While HTML does involve hyperlinks, this is not the correct expansion of the acronym. Home Tool Markup Language Incorrect. This is not what HTML stands for. Hyper Transfer Markup Language Incorrect. This is not the correct expansion of HTML. ';
- static const prompt_quizgen_truefalse_example =
- 'Please use this XML sample as a template for your response: World War 2 Question 1 The Normandy landings, also known as D-Day, took place on June 6, 1944. True Correct! The Normandy landings, codenamed Operation Neptune, indeed took place on June 6, 1944, marking the beginning of the Allied invasion of Nazi-occupied Western Europe. False Incorrect. The Normandy landings, also known as D-Day, did occur on June 6, 1944. This date marks a crucial turning point in World War 2. World War 2 Question 2 The atomic bombs dropped on Hiroshima and Nagasaki were codenamed "Little Boy" and "Fat Man" respectively. True Correct! "Little Boy" was the codename for the bomb dropped on Hiroshima on August 6, 1945, while "Fat Man" was dropped on Nagasaki on August 9, 1945. False Incorrect. The atomic bombs were indeed codenamed "Little Boy" (Hiroshima) and "Fat Man" (Nagasaki). These were the only two nuclear weapons ever used in warfare. World War 2 Question 3 The Battle of Stalingrad ended with a decisive Soviet victory, marking a turning point on the Eastern Front. True Correct! The Battle of Stalingrad, which lasted from August 23, 1942, to February 2, 1943, ended in a decisive Soviet victory and is considered a major turning point in World War 2. False Incorrect. The Battle of Stalingrad did indeed end with a decisive Soviet victory, which marked a crucial turning point on the Eastern Front and in World War 2 as a whole. ';
- static const prompt_quizgen_shortanswer_example =
- 'Please use this XML sample as a template for your response: Combinatorics Question 1 In how many ways can 5 distinct books be arranged on a shelf? 120 5! Combinatorics Question 2 How many different 4-digit numbers can be formed using the digits 1, 2, 3, 4, 5, 6 if no digit can be repeated? 360 ';
- static const prompt_quizgen_essay_example =
- 'Please use this XML sample as a template for your response: American Revolution Essay Write a well-structured essay analyzing the key factors that led to the American Revolution and its long-term impact on the formation of the United States. Your essay should address the following points:
Discuss at least three major events or policies that contributed to growing tensions between the American colonies and Great Britain in the decade leading up to 1776. Explain the significance of the Declaration of Independence, both as a philosophical document and as a catalyst for revolutionary action. Analyze the role of key figures such as George Washington, Thomas Jefferson, and Benjamin Franklin in shaping the course of the revolution. Evaluate the impact of the American Revolution on the political, social, and economic structures of the newly formed United States. Consider how the ideals of the American Revolution influenced later democratic movements both in the United States and around the world. Your essay should be approximately 1000-1200 words in length. Use specific historical examples to support your arguments, and be sure to cite any sources you use.
]]> This essay requires you to demonstrate your understanding of the causes and consequences of the American Revolution, as well as your ability to analyze historical events and their significance. 100 0 0 editor 1 15 0 0 Grading Criteria: Clear thesis statement and well-structured argument: 20 points Accurate discussion of pre-revolutionary events and policies: 20 points Insightful analysis of the Declaration of Independence: 15 points Thoughtful examination of key revolutionary figures: 15 points Comprehensive evaluation of the revolution\'s impact: 20 points Consideration of the revolution\'s broader influence: 10 points Use of specific historical examples and proper citations: 10 points ]]> ';
- static const prompt_quizgen_coding_example =
- 'Please use this XML sample as a template for your response: Dart List Manipulation: Filtering Even Numbers Write a Dart function called filterEvenNumbers that takes a list of integers as input and returns a new list containing only the even numbers from the input list. Your function should use the following signature: List filterEvenNumbers(List numbers) Requirements:
Use Dart\'s list methods to implement the filtering logic. The original list should not be modified. If the input list is empty, return an empty list. Include comments to explain your code. Test your function with different input lists to ensure it works correctly.
]]> 100 0 0 editor 1 30 0 0 void main() { List numbers1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; List result1 = filterEvenNumbers(numbers1); print(\'Test 1: \$result1\'); // Expected: [2, 4, 6, 8, 10] List numbers2 = [1, 3, 5, 7, 9]; List result2 = filterEvenNumbers(numbers2); print(\'Test 2: \$result2\'); // Expected: [] List numbers3 = []; List result3 = filterEvenNumbers(numbers3); print(\'Test 3: \$result3\'); // Expected: [] } List filterEvenNumbers(List numbers) { // Your implementation here } ]]> filterEvenNumbers(List numbers) { // Your code here } void main() { // Test your function here } ]]> ';
- // 'Please use this XML sample as a template for your response: Maximum grade goes here 0 0 editorfilepicker 1 15 0 1 Level 4, the highest criteria score goes here. Level 3 criteria score goes here Level 2 criteria score goes here Level 1, the lowest criteria score goes here Input goes hereExpected output goes here ';
- // 'Please use this XML sample as a template for your response: Recursive Functions in Dart Implement a recursive function in Dart that calculates the nth Fibonacci number. The Fibonacci sequence is defined as follows: the first two numbers are 0 and 1, and each subsequent number is the sum of the two preceding ones. Your function should take an integer n as input and return the nth Fibonacci number. Additionally, explain the time complexity of your recursive solution and suggest how you might optimize it using memoization. Provide your code implementation and explanation below. A well-implemented recursive Fibonacci function should correctly calculate the nth Fibonacci number. The explanation should discuss the exponential time complexity of the naive recursive solution and how memoization can improve it to linear time complexity. 10 0 0 editor 1 15 0 ';
-
- static const prompt_assistant = """
- You are EduLense, a specialized e-learning assistant. You have these possible functions:
- 1) getUserCourses()
- 2) getCourseParticipants(courseId=?)
- 3) getQuizzes(courseID=?, quizTopicId=?)
- 4) getQuizGradesForParticipants(courseId=?, quizId=?)
-
- If you need to call a function to fulfill the user's request,
- respond EXACTLY in the format (on a single line):
- CALL functionName(arg1=value1, arg2=value2)
-
- You can make multiple function calls if you need step-by-step data:
- - For example, if the user says "What quizzes do I have for my Math course?",
- you might first call:
- CALL getUserCourses()
- to find the course ID for "Math" (like 101).
- - Then parse that result. If you see "Math 101 (ID: 101, quizTopicId: 201)",
- call:
- CALL getQuizzes(courseID=101, quizTopicId=)
- - Once you have the quizzes, produce a final text answer (like "You have a midterm quiz...").
-
- Avoid calling getUserCourses() repeatedly if you already have the data.
-
- If you do not need any function call, just provide a direct answer in plain text.
-
- Example conversation:
- User: "Who is in my Math course?"
- You might do:
- CALL getUserCourses()
- (assume the result shows "Math 101" => ID=101)
- Then:
- CALL getCourseParticipants(courseId=101)
- Finally, once you get the participants, answer in plain text.
-
- Always provide a final text summary once you have enough data from function calls.
- Do not keep calling functions indefinitely.
- """;
-
- static String _FormatQuestionNumbers(AssignmentForm form) {
- String ret = 'The quiz should have ';
- List questionsCount = [];
- int count;
- if (form.multipleChoiceCount > 0) {
- count = form.multipleChoiceCount;
- print(count);
- if (count == 1) {
- questionsCount.add("$count multiple choice question");
- } else {
- questionsCount.add("$count multiple choice questions");
- }
- } if (form.trueFalseCount > 0) {
- count = form.trueFalseCount;
- if (count == 1) {
- questionsCount.add("$count true or false question");
- } else {
- questionsCount.add("$count true or false questions");
- }
- }
- if (form.shortAnswerCount > 0) {
- count = form.shortAnswerCount;
- if (count == 1) {
- questionsCount.add("$count short answer question");
- } else {
- questionsCount.add("$count short answer questions");
- }
- }
- return "$ret${questionsCount.join(", ")}. ";
- }
-
- static String generatePrompt(AssignmentForm form) {
- String prompt = prompt_quizgen_other + _FormatQuestionNumbers(form) + prompt_quizgen_choice_example;
- prompt = prompt
- .replaceAll('[subject]', form.subject)
- .replaceAll('[topic]', form.topic)
- .replaceAll('[gradelevel]', form.gradeLevel)
- .replaceAll('[maxgrade]', form.maximumGrade.toString());
-
- if (form.assignmentCount != null) {
- prompt = prompt.replaceAll(
- '[numassignments]', form.assignmentCount.toString());
- }
- if (form.gradingCriteria != null) {
- prompt = prompt.replaceAll('[rubriccriteria]', form.gradingCriteria!);
- }
- if (form.codingLanguage != null) {
- prompt = prompt.replaceAll('[codinglanguage]', form.codingLanguage!);
- }
- prompt += prompt_quizgen_xmlonly;
- return prompt;
- }
-}
\ No newline at end of file
diff --git a/team_a/teamA/lib/Api/lms/constants/learning_lens.constants.dart b/team_a/teamA/lib/Api/lms/constants/learning_lens.constants.dart
deleted file mode 100644
index 82453959..00000000
--- a/team_a/teamA/lib/Api/lms/constants/learning_lens.constants.dart
+++ /dev/null
@@ -1,4 +0,0 @@
-class LearningLensConstants{
- static const List gradeLevels = ['Kindergarten', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'];
-
-}
\ No newline at end of file
diff --git a/team_a/teamA/lib/Api/lms/enum/assignee_mode_enum.dart b/team_a/teamA/lib/Api/lms/enum/assignee_mode_enum.dart
deleted file mode 100644
index 4cf9e307..00000000
--- a/team_a/teamA/lib/Api/lms/enum/assignee_mode_enum.dart
+++ /dev/null
@@ -1,6 +0,0 @@
-//Enum for AssigneeMode
-enum AssigneeMode {
- ASSIGNEE_MODE_UNSPECIFIED,
- ALL_STUDENTS,
- INDIVIDUAL_STUDENTS
-}
\ No newline at end of file
diff --git a/team_a/teamA/lib/Api/lms/enum/course_state_enum.dart b/team_a/teamA/lib/Api/lms/enum/course_state_enum.dart
deleted file mode 100644
index d1da73e6..00000000
--- a/team_a/teamA/lib/Api/lms/enum/course_state_enum.dart
+++ /dev/null
@@ -1,8 +0,0 @@
-//Enum for CourseState
-enum CourseState {
- COURSE_STATE_UNSPECIFIED,
- ACTIVE,
- ARCHIVED,
- PROVISIONED,
- DECLINED
-}
\ No newline at end of file
diff --git a/team_a/teamA/lib/Api/lms/enum/course_work_state_enum.dart b/team_a/teamA/lib/Api/lms/enum/course_work_state_enum.dart
deleted file mode 100644
index f6413959..00000000
--- a/team_a/teamA/lib/Api/lms/enum/course_work_state_enum.dart
+++ /dev/null
@@ -1,7 +0,0 @@
-//Enum for CourseWorkState
-enum CourseWorkState {
- COURSE_WORK_STATE_UNSPECIFIED,
- PUBLISHED,
- DRAFT,
- DELETED
-}
\ No newline at end of file
diff --git a/team_a/teamA/lib/Api/lms/enum/course_work_type_enum.dart b/team_a/teamA/lib/Api/lms/enum/course_work_type_enum.dart
deleted file mode 100644
index 0e6c8eee..00000000
--- a/team_a/teamA/lib/Api/lms/enum/course_work_type_enum.dart
+++ /dev/null
@@ -1,7 +0,0 @@
-//Enum for CourseWorkType
-enum CourseWorkType {
- COURSE_WORK_TYPE_UNSPECIFIED,
- ASSIGNMENT,
- SHORT_ANSWER_QUESTION,
- MULTIPLE_CHOICE_QUESTION
-}
\ No newline at end of file
diff --git a/team_a/teamA/lib/Api/lms/enum/lms_enum.dart b/team_a/teamA/lib/Api/lms/enum/lms_enum.dart
deleted file mode 100644
index 35de018f..00000000
--- a/team_a/teamA/lib/Api/lms/enum/lms_enum.dart
+++ /dev/null
@@ -1,4 +0,0 @@
-enum LmsType {
- MOODLE,
- GOOGLE,
-}
\ No newline at end of file
diff --git a/team_a/teamA/lib/Api/lms/enum/preview_version_enum.dart b/team_a/teamA/lib/Api/lms/enum/preview_version_enum.dart
deleted file mode 100644
index f09549a1..00000000
--- a/team_a/teamA/lib/Api/lms/enum/preview_version_enum.dart
+++ /dev/null
@@ -1,5 +0,0 @@
-//Enum for PreviewVersion
-enum PreviewVersion {
- PREVIEW_VERSION_UNSPECIFIED,
- PUBLISHED_VERSION
-}
diff --git a/team_a/teamA/lib/Api/lms/enum/submission_mod_mode_enum.dart b/team_a/teamA/lib/Api/lms/enum/submission_mod_mode_enum.dart
deleted file mode 100644
index aaebce84..00000000
--- a/team_a/teamA/lib/Api/lms/enum/submission_mod_mode_enum.dart
+++ /dev/null
@@ -1,6 +0,0 @@
-//Enum for SubmissionModificationMode
-enum SubmissionModificationMode {
- SUBMISSION_MODIFICATION_MODE_UNSPECIFIED,
- MODIFIABLE_UNTIL_TURNED_IN,
- MODIFIABLE_AFTER_TURNED_IN
-}
\ No newline at end of file
diff --git a/team_a/teamA/lib/Api/lms/factory/lms_factory.dart b/team_a/teamA/lib/Api/lms/factory/lms_factory.dart
deleted file mode 100644
index 128da031..00000000
--- a/team_a/teamA/lib/Api/lms/factory/lms_factory.dart
+++ /dev/null
@@ -1,34 +0,0 @@
-import 'package:learninglens_app/Api/lms/enum/lms_enum.dart';
-import 'package:learninglens_app/Api/lms/lms_interface.dart';
-import 'package:learninglens_app/Api/lms/moodle/moodle_lms_service.dart';
-import 'package:learninglens_app/Api/lms/google_classroom/google_lms_service.dart';
-import 'package:learninglens_app/services/local_storage_service.dart';
-
-class LmsFactory {
-
- static MoodleLmsService _lmsServiceMoodle = MoodleLmsService();
- static GoogleLmsService _lmsServiceGoogle = GoogleLmsService();
-
- static LmsInterface getLmsService() {
- LmsType lmsType = LocalStorageService.getSelectedClassroom();
-
- switch (lmsType) {
- // TODO: Do we need this or can we just return Moodle as a default with the default: case?
- // case LmsType.MOODLE:
- // return getLmsServiceMoodle();
- case LmsType.GOOGLE:
- return getLmsServiceGoogle();
- default:
- // print('LMS type not found, defaulting to Moodle');
- return getLmsServiceMoodle();
- }
- }
-
- static MoodleLmsService getLmsServiceMoodle() {
- return _lmsServiceMoodle;
- }
-
- static GoogleLmsService getLmsServiceGoogle() {
- return _lmsServiceGoogle;
- }
-}
\ No newline at end of file
diff --git a/team_a/teamA/lib/Api/lms/google_classroom/google_classroom_api.dart b/team_a/teamA/lib/Api/lms/google_classroom/google_classroom_api.dart
deleted file mode 100644
index a4440515..00000000
--- a/team_a/teamA/lib/Api/lms/google_classroom/google_classroom_api.dart
+++ /dev/null
@@ -1,747 +0,0 @@
-import 'dart:convert';
-import 'package:http/http.dart' as http;
-import 'package:learninglens_app/beans/lesson_plan.dart';
-import 'package:logging/logging.dart';
-import 'package:learninglens_app/services/local_storage_service.dart';
-class GoogleClassroomApi {
- // final MainController _controller = MainController(); ***** Not used *****
-
- Future _getToken() async {
- final token = LocalStorageService.getGoogleAccessToken();
- if (token == null) {
- print(
- 'Error: No valid OAuth token. Ensure the required scopes are enabled. Token null');
- }
- return token;
- }
-
- // -----------------------------------------------------------------------
- // Creates a new Google Form
- // -----------------------------------------------------------------------
- Future?> createForm(String? teacherFolderId, String title) async {
- final token = await _getToken();
- if (token == null) return null;
-
- final url = Uri.parse('https://forms.googleapis.com/v1/forms');
- final headers = {
- 'Authorization': 'Bearer $token',
- 'Content-Type': 'application/json',
- };
-
- final body = jsonEncode({
- 'info': {'title': title, 'documentTitle': title},
- });
-
- try {
- final response = await http.post(url, headers: headers, body: body);
-
- print("Response Status: ${response.statusCode}");
- print("Response Body: ${response.body}");
- print('teacherFolderId: $teacherFolderId');
-
- if (response.statusCode == 200) {
- final data = jsonDecode(response.body);
-
- // Move the folder to where GC expects it to be.
- final driveUrl = Uri.https(
- 'www.googleapis.com',
- '/drive/v3/files/${data['formId']}',
- {
- 'addParents': teacherFolderId,
- 'removeParents': 'root',
- },
- );
- final driveHeaders = {
- 'Authorization': 'Bearer $token',
- 'Content-Type': 'application/json',
- };
- final driveBody = jsonEncode({
- 'addParents': teacherFolderId, // New parent folder
- 'removeParents': 'root',
- });
-
- // Remove from root (optional: fetch current parents if needed)
- final driveResponse = await http.patch(
- driveUrl,
- headers: driveHeaders,
- body: driveBody,
- );
- print('***********************************************************************************');
- print("Drive Update Status: ${driveResponse.statusCode}");
- print("Drive Update Body: ${driveResponse.body}");
-
-
- return data;
- } else {
- print('Form creation failed: ${response.statusCode}');
- return null;
- }
- } catch (e) {
- print('Error creating form: $e');
- return null;
- }
- }
-
- // -----------------------------------------------------------------------
- // Batches the update
- // -----------------------------------------------------------------------
- Future?> batchUpdateForm(
- String formId, List> requests) async {
- final token = await _getToken();
- if (token == null) return null;
-
- final url =
- Uri.parse('https://forms.googleapis.com/v1/forms/$formId:batchUpdate');
- final headers = {
- 'Authorization': 'Bearer $token',
- 'Content-Type': 'application/json',
- };
-
- final body = jsonEncode({
- 'requests': requests,
- });
-
- try {
- final response = await http.post(url, headers: headers, body: body);
-
- print("Response Status: ${response.statusCode}");
- print("Response Body: ${response.body}");
-
- if (response.statusCode == 200) {
- final data = jsonDecode(response.body);
- return data;
- } else {
- print('Form batch update failed: ${response.statusCode}');
- return null;
- }
- } catch (e) {
- print('Error updating form settings: $e');
- return null;
- }
- }
-
- //Future?> createAssignment(String courseid, String sectionid, String assignmentName, String startdate, String enddate, String rubricJson, String description);
- // -----------------------------------------------------------------------
-// Creates a Classroom assignment and links the form
-// -----------------------------------------------------------------------
- // Future createAssignment(String courseId, String title,
- // String description, String responderUri, DateTime dueDate) async {
- // //Change the date parameter to DateTime
- // // Changed formId to responderUri in the parameters
- // final token = await _getToken();
- // if (token == null) return null;
-
- // final url = Uri.parse(
- // 'https://classroom.googleapis.com/v1/courses/$courseId/courseWork');
- // final headers = {
- // 'Authorization': 'Bearer $token',
- // 'Content-Type': 'application/json',
- // };
- // final year = dueDate.year;
- // final month = dueDate.month;
- // final day = dueDate.day;
- // final hours = dueDate.hour;
- // final minutes = dueDate.minute;
-
- // final body = jsonEncode({
- // "title": title,
- // "description": description,
- // "workType": "ASSIGNMENT",
- // "state": "PUBLISHED",
- // "dueDate": {"year": year, "month": month, "day": day},
- // "dueTime": {"hours": hours, "minutes": minutes, "seconds": 0},
- // "materials": [
- // {
- // "link": {"url": responderUri}
- // } // Use the responderUri directly - THIS IS THE FIX!
- // ]
- // });
-
- // try {
- // final response = await http.post(url, headers: headers, body: body);
-
- // print("Response Status: ${response.statusCode}");
- // print("Response Body: ${response.body}");
-
- // if (response.statusCode == 200) {
- // final data = jsonDecode(response.body);
- // final assignmentId = data['id'];
- // print('Assignment created successfully with ID: $assignmentId');
- // return assignmentId;
- // } else {
- // print('Assignment creation failed: ${response.statusCode}');
- // return null;
- // }
- // } catch (e) {
- // print('Error creating assignment: $e');
- // return null;
- // }
- // }
-
- Future createAssignment(String courseId, String title,
- String description, String responderUri, String dueDate) async {
- final log = Logger('GoogleClassroomApi creating Assignments');
- log.info('We are calling createAssignment from Google Classroom.');
- final token = await _getToken();
- if (token == null) return null;
-
- final url = Uri.parse(
- 'https://classroom.googleapis.com/v1/courses/$courseId/courseWork');
- final headers = {
- 'Authorization': 'Bearer $token',
- 'Content-Type': 'application/json',
- };
-
- // Parse the dueDate string to extract year, month, day, hours, and minutes
- List dateParts = dueDate.split('-');
- int year = int.parse(dateParts[0]);
- int month = int.parse(dateParts[1]);
- int day = int.parse(dateParts[2]);
- int hours = int.parse(dateParts[3]);
- int minutes = int.parse(dateParts[4]);
- String? topicId = await getTopicId(courseId, 'Quiz') ?? '755868506953';
-
- print('topic id is : $topicId');
-
- //String? topicId ='755868506953';
-
-
- final body = jsonEncode({
- "title": title,
- "description": description,
- "topicId": topicId,
- "workType": "ASSIGNMENT",
- "state": "PUBLISHED",
- "dueDate": {"year": year, "month": month, "day": day},
- "dueTime": {"hours": hours, "minutes": minutes, "seconds": 0},
- "materials": [
- {
- "link": {"url": responderUri}
- } // Use the responderUri directly - THIS IS THE FIX!
- ]
- });
-
-print('body for creating assignment is : $body');
- try {
- final response = await http.post(url, headers: headers, body: body);
-
- print("Response Status: ${response.statusCode}");
- print("Response Body: ${response.body}");
-
- if (response.statusCode == 200) {
- final data = jsonDecode(response.body);
- final assignmentId = data['id'];
- print('Assignment created successfully with ID: $assignmentId');
- return assignmentId;
- } else {
-
- print('Assignment creation failed inside createAssignment method : ${response.statusCode}');
- print(response.body);
- return null;
- }
- } catch (e) {
- print('Error creating assignment: $e');
- return null;
- }
- }
-
- Future getTopicIdByCreating(String courseId, String title) async {
- final log = Logger('GoogleClassroomApi creating Topics');
- log.info('Calling createTopic from Google Classroom.');
-
- // Obtain the OAuth2 token
- final token = await _getToken();
- if (token == null) {
- print('Failed to obtain token.');
- return null;
- }
-
- final url = Uri.parse('https://classroom.googleapis.com/v1/courses/$courseId/topics');
- final headers = {
- 'Authorization': 'Bearer $token',
- 'Content-Type': 'application/json',
- };
- final body = jsonEncode({
- "name": title,
- });
-
- try {
- // Make the POST request to create the topic
- final response = await http.post(url, headers: headers, body: body);
-
- if (response.statusCode == 200) {
- // Parse the response and extract the topicId
- final data = jsonDecode(response.body);
- return data['topicId'];
- } else {
- print('Failed to create topic. Status code: ${response.statusCode}, Response: ${response.body}');
- }
- } catch (e) {
- print('Error creating topic: $e');
- }
-
- return null;
-}
-Future getTopicId(String courseId, String title) async {
- final log = Logger('GoogleClassroomApi getting TopicId');
- log.info('Calling listTopics from Google Classroom.');
-
- final token = await _getToken();
- if (token == null) {
- print('Failed to obtain token.');
- return null;
- }
-
- final url = Uri.parse('https://classroom.googleapis.com/v1/courses/$courseId/topics');
- final headers = {
- 'Authorization': 'Bearer $token',
- 'Accept': 'application/json',
- };
-
- try {
- final response = await http.get(url, headers: headers);
-
- if (response.statusCode == 200) {
- final data = jsonDecode(response.body);
- final topics = data['topic'] as List;
-
- for (var topic in topics) {
- if (topic['name'] == title) {
- return topic['topicId'];
- }
- }
-
- log.warning('Topic with name "$title" not found. So i need to create a new topic');
- return getTopicIdByCreating(courseId, title);
- } else {
- print('Failed to list topics. Status code: ${response.statusCode}, Response: ${response.body}');
- }
- } catch (e) {
- print('Error listing topics: $e');
- }
-
- return null;
-}
-/*
- Future createCourseWorkMaterial(String courseId, String accessToken, String title, ) async {
- final url = Uri.parse('https://classroom.googleapis.com/v1/courses/$courseId/courseWorkMaterials');
-
- final headers = {
- 'Authorization': 'Bearer $accessToken',
- 'Content-Type': 'application/json',
- };
-
- final body = jsonEncode({
- "title": "My New Course Material",
- "description": "This is a description of the material.",
- "materials": [
- {
- "link": {
- "url": "https://example.com/material"
- }
- }
- ],
- "state": "PUBLISHED",
- "scheduledTime": "2024-03-15T10:00:00Z",
- "topicId": "your_topic_id"
- });
-
- final response = await http.post(url, headers: headers, body: body);
-
- if (response.statusCode == 200) {
- // Success! The course work material was created.
- print('Course work material created successfully!');
- print(response.body); // You might want to parse the response JSON here.
- } else {
- // Error! Something went wrong.
- print('Error creating course work material: ${response.statusCode}');
- print(response.body);
- }
- }
-*/
- // -----------------------------------------------------------------------
- // Course Work Realated Methods
- // -----------------------------------------------------------------------
-
-
- Future createCourseWorkMaterial(String courseId, String title, String description, String materialUrl,
- {String? topicId, DateTime? scheduledTime}) async {
- print('Creating material: $title');
-
- final accessToken = await _getToken();
- if (accessToken == null) return null;
-
- final googleClassroomApi = GoogleClassroomApi();
- String? topicID = await googleClassroomApi.getTopicId(courseId, "Lesson Plans");
-
- final url = Uri.parse('https://classroom.googleapis.com/v1/courses/$courseId/courseWorkMaterials');
- final headers = {'Authorization': 'Bearer $accessToken', 'Content-Type': 'application/json'};
- final body = jsonEncode({
- "title": title,
- "description": description,
- "state": "PUBLISHED",
- if (scheduledTime != null) "scheduledTime": scheduledTime.toUtc().toIso8601String(),
- if (topicID != null) "topicId": topicID,
- });
-
- try {
- final response = await http.post(url, headers: headers, body: body);
- if (response.statusCode == 200) {
- return jsonDecode(response.body)['id'];
- } else {
- print('Error creating material: ${response.statusCode}');
- return null;
- }
- } catch (e) {
- print('Error creating material: $e');
- return null;
- }
- }
-
-/*
-Future updateCourseWorkMaterial(String courseId, String materialId, String? title, String? description,
- {String? topicId, DateTime? scheduledTime, String? state}) async {
- print('Updating material: $materialId');
-
- final accessToken = await _getToken();
- if (accessToken == null) return;
-
- final url = Uri.parse('https://classroom.googleapis.com/v1/courses/$courseId/courseWorkMaterials/$materialId');
- print('Update URL: $url');
-
- final updateMask = _generateUpdateMask(title, description, topicId, scheduledTime, state);
- final headers = {
- 'Authorization': 'Bearer $accessToken',
- 'Content-Type': 'application/json',
- 'updateMask': updateMask,
- };
- print('Update Mask: $updateMask');
-
- final Map requestBody = {};
- if (title != null) requestBody["title"] = title;
- if (description != null) requestBody["description"] = description;
- // if (state != null) requestBody["state"] = state;
- // if (scheduledTime != null) requestBody["scheduledTime"] = scheduledTime.toUtc().toIso8601String();
- // if (topicId != null) requestBody["topicId"] = topicId;
-
- final body = jsonEncode(requestBody);
-
- print('Request Body:=> $body');
-
- try {
- print('Inside the try block');
- final response = await http.patch(url, headers: headers, body: body);
- print('Response Status Code: ${response.statusCode}');
- print('Response Body: ${response.body}');
-
- if (response.statusCode == 200) {
- getLessonPlan(courseId);
- } else {
- print('Error updating material: ${response.statusCode}');
- }
- } catch (e) {
- print('Error updating material: $e');
- }
-}
-*/
-Future updateCourseWorkMaterial(
- String courseId,
- String materialId,
- String? title,
- String? description, {
- String? topicId,
- DateTime? scheduledTime,
- String? state,
- }) async {
- print('Updating material: $materialId');
-
- final accessToken = await _getToken();
- if (accessToken == null) {
- print('No valid access token found.');
- return;
- }
-
- // Generate the updateMask
- final updateMask = _generateUpdateMask(title, description, topicId, scheduledTime, state);
- final url = Uri.parse(
- 'https://classroom.googleapis.com/v1/courses/$courseId/courseWorkMaterials/$materialId?updateMask=$updateMask');
- print('Update URL: $url');
-
- final headers = {
- 'Authorization': 'Bearer $accessToken',
- 'Content-Type': 'application/json',
- };
-
- final Map requestBody = {};
- if (title != null) requestBody["title"] = title;
- if (description != null) requestBody["description"] = description;
- // Uncomment and use these if needed
- // if (state != null) requestBody["state"] = state;
- // if (scheduledTime != null) requestBody["scheduledTime"] = scheduledTime.toUtc().toIso8601String();
- // if (topicId != null) requestBody["topicId"] = topicId;
-
- final body = jsonEncode(requestBody);
- print('Request Body: $body');
-
- final client = http.Client();
- try {
- final response = await client.patch(url, headers: headers, body: body);
- print('Response Status Code: ${response.statusCode}');
- print('Response Body: ${response.body}');
-
- if (response.statusCode == 200) {
- print('Material updated successfully.');
- // Note: We’ll refresh the UI in the calling code, not here
- } else {
- print('Error updating material: ${response.statusCode} - ${response.body}');
- }
- } catch (e) {
- print('Detailed error updating material: $e');
- } finally {
- client.close();
- }
-}
-
-// Helper method to generate updateMask (unchanged)
-String _generateUpdateMask(String? title, String? description, String? topicId, DateTime? scheduledTime, String? state) {
- List fields = [];
- if (title != null) fields.add('title');
- if (description != null) fields.add('description');
- if (topicId != null) fields.add('topicId');
- if (scheduledTime != null) fields.add('scheduledTime');
- if (state != null) fields.add('state');
- return fields.join(',');
-}
-
- Future deleteCourseWorkMaterial(String courseId, String materialId) async {
- print('Deleting material: $materialId');
- final accessToken = await _getToken();
- if (accessToken == null) return;
-
- final url = Uri.parse('https://classroom.googleapis.com/v1/courses/$courseId/courseWorkMaterials/$materialId');
- final headers = {'Authorization': 'Bearer $accessToken', 'Content-Type': 'application/json'};
-
- try {
- final response = await http.delete(url, headers: headers);
-
- print(response.statusCode);
- print(response.body);
-
- if (response.statusCode == 200 || response.statusCode == 204) {
- getLessonPlan(courseId);
- } else {
- print('Error deleting material: ${response.statusCode}');
- }
- } catch (e) {
- print('Error deleting material: $e');
- }
- }
-
-
-
-Future getLessonPlan(String courseId) async {
- LessonPlan lessonPlan = LessonPlan.empty();
- try {
- final accessToken = await _getToken();
- if (accessToken == null) return;
-
- final url = Uri.parse('https://classroom.googleapis.com/v1/courses/$courseId/courseWorkMaterials');
- final headers = {'Authorization': 'Bearer $accessToken', 'Content-Type': 'application/json'};
- final response = await http.get(url, headers: headers);
-
- if (response.statusCode == 200) {
- final data = jsonDecode(response.body);
-
- if (data.containsKey('courseWorkMaterial')) {
- List lessonPlans = (data['courseWorkMaterial'] as List)
- .map((item) => lessonPlan.fromGoogleJson(item as Map))
- .toList();
-
- print('Lesson Plans: $lessonPlans');
- } else {
- print('No coursework materials found.');
- }
- } else {
- print('Error loading coursework materials: ${response.statusCode}');
- }
- } catch (e) {
- print('Error loading coursework materials: $e');
- }
-}
-
-
-// -----------------------------------------------------------------------
-// retrive the quiz questions
-// -----------------------------------------------------------------------
- Future?> getQuizQuestions(String courseId, String quizId) async {
- final token = await _getToken();
- if (token == null) return null;
-
- final url = Uri.parse('https://classroom.googleapis.com/v1/courses/$courseId/courseWork/$quizId');
- final headers = {
- 'Authorization': 'Bearer $token',
- 'Content-Type': 'application/json',
- };
-
- try {
- final response = await http.get(url, headers: headers);
-
- print("Response Status: ${response.statusCode}");
- print("Response Body: ${response.body}");
-
- if (response.statusCode == 200) {
- final data = jsonDecode(response.body);
- return data;
- } else {
- print('Quiz questions retrieval failed: ${response.statusCode}');
- return null;
- }
- } catch (e) {
- print('Error retrieving quiz questions: $e');
- return null;
- }
- }
-
- // -----------------------------------------------------------------------
- // Updates the course work
- // -----------------------------------------------------------------------
- Future?> updateCourseWork(String courseId, String courseWorkId, String title, String description,
- String? dueDate, String? dueTime, String? rubricJson, String? state) async {
- final token = await _getToken();
- if (token == null) return null;
-
- final url = Uri.parse('https://classroom.googleapis.com/v1/courses/$courseId/courseWork/$courseWorkId');
- final headers = {
- 'Authorization': 'Bearer $token',
- 'Content-Type': 'application/json',
- };
-
- final body = jsonEncode({
- 'title': title,
- 'description': description,
- 'dueDate': dueDate,
- 'dueTime': dueTime,
- 'rubric': rubricJson,
- 'state': state,
- });
-
- try {
- final response = await http.put(url, headers: headers, body: body);
-
- print("Response Status: ${response.statusCode}");
- print("Response Body: ${response.body}");
-
- if (response.statusCode == 200) {
- final data = jsonDecode(response.body);
- return data;
- } else {
- print('Course work update failed: ${response.statusCode}');
- return null;
- }
- } catch (e) {
- print('Error updating course work: $e');
- return null;
- }
- }
-
- // -----------------------------------------------------------------------
-
- // Method to retrieve Google Form questions from an assignment
- // -----------------------------------------------------------------------
-
-
-Future> getAssignmentFormQuestions(String? courseId, String? courseWorkId) async {
- // "courseId": "750797786103",
- //"id": "757589683856",
- try {
- // Step 1: Retrieve the stored access token
- final token = await _getToken();
- print('Token: $token'); // Debug output
-
- if (token == null) {
- throw Exception('No access token found. Please log in again.');
- }
-
- // Step 2: Fetch assignment details from Google Classroom API
- final courseworkUrl =
- 'https://classroom.googleapis.com/v1/courses/$courseId/courseWork/$courseWorkId';
- final courseworkResponse = await http.get(
- Uri.parse(courseworkUrl),
- headers: {
- 'Authorization': 'Bearer $token',
- 'Content-Type': 'application/json',
- },
- );
-
- print('Coursework URL: $courseworkUrl'); // Debug output
- print('Coursework Response: ${courseworkResponse.body}'); // Debug output
-
- if (courseworkResponse.statusCode != 200) {
- throw Exception(
- 'Failed to fetch coursework: ${courseworkResponse.statusCode} - ${courseworkResponse.body}');
- }
-
- final courseworkData = jsonDecode(courseworkResponse.body);
- print('Coursework Data: $courseworkData'); // Debug output
-
- // Step 3: Extract Form URL from materials
- String? formUrl;
- if (courseworkData['materials'] != null && courseworkData['materials'].isNotEmpty) {
- for (var material in courseworkData['materials']) {
- if (material['link'] != null && material['link']['url'] != null) {
- final url = material['link']['url'];
- if (url.contains('docs.google.com/forms')) {
- formUrl = url;
- break;
- }
- }
- }
- }
-
- if (formUrl == null) {
- throw Exception('No Google Form found in assignment materials.');
- }
- print('Form URL: $formUrl'); // Debug output
-
- // Step 4: Extract Form ID from the URL
- final formId = formUrl.split('/d/e/')[1].split('/')[0];
- print('Form ID: $formId'); // Debug output
-
- // Step 5: Fetch form details from Google Forms API
- final formsUrl = 'https://forms.googleapis.com/v1/forms/$formId';
- final formsResponse = await http.get(
- Uri.parse(formsUrl),
- headers: {
- 'Authorization': 'Bearer $token',
- 'Content-Type': 'application/json',
- },
- );
-
- if (formsResponse.statusCode != 200) {
- throw Exception(
- 'Failed to fetch form: ${formsResponse.statusCode} - ${formsResponse.body}');
- }
-
- final formData = jsonDecode(formsResponse.body);
- print('Form Data: $formData'); // Debug output
-
- // Step 6: Extract questions from the form
- List questions = [];
- if (formData['items'] != null) {
- for (var item in formData['items']) {
- if (item['questionItem'] != null && item['questionItem']['question'] != null) {
- questions.add(item['questionItem']['question']['text']);
- }
- }
- }
-
- return questions;
- } catch (e) {
- print('Error retrieving form questions: $e');
- return [];
- }
-}
-
-
-
-}
diff --git a/team_a/teamA/lib/Api/lms/google_classroom/google_lms_service.dart b/team_a/teamA/lib/Api/lms/google_classroom/google_lms_service.dart
deleted file mode 100644
index 2c74b7b1..00000000
--- a/team_a/teamA/lib/Api/lms/google_classroom/google_lms_service.dart
+++ /dev/null
@@ -1,1098 +0,0 @@
-import 'dart:convert';
-import 'dart:io';
-import 'package:http/http.dart' as http;
-import 'package:learninglens_app/beans/g_question_form_data.dart';
-import 'package:logger/logger.dart';
-import 'package:learninglens_app/beans/quiz_type.dart';
-import 'package:xml/xml.dart' as xml;
-import 'package:google_sign_in/google_sign_in.dart';
-import 'package:learninglens_app/Api/lms/lms_interface.dart';
-import 'package:learninglens_app/beans/course.dart';
-import 'package:learninglens_app/beans/quiz.dart';
-import 'package:learninglens_app/beans/assignment.dart';
-import 'package:learninglens_app/beans/participant.dart';
-import 'package:learninglens_app/beans/submission_status.dart';
-import 'package:learninglens_app/beans/grade.dart';
-import 'package:learninglens_app/beans/submission.dart';
-import 'package:learninglens_app/beans/submission_with_grade.dart';
-import 'package:learninglens_app/beans/moodle_rubric.dart';
-import 'package:learninglens_app/services/api_service.dart';
-import 'package:learninglens_app/services/local_storage_service.dart';
-import 'package:learninglens_app/Api/lms/google_classroom/google_classroom_api.dart'; // Import the updated API
-
-/// A Singleton class for Moodle API access implementing [LmsInterface].
-class GoogleLmsService extends LmsInterface {
- // Needed??
- final GoogleClassroomApi _classroomApi = GoogleClassroomApi();
-
- // ****************************************************************************************
- // Static / Singleton internals
- // ****************************************************************************************
-
- static final GoogleLmsService _instance = GoogleLmsService._internal();
-
- /// The singleton accessor.
- factory GoogleLmsService() => _instance;
-
- /// Private named constructor.
- GoogleLmsService._internal();
-
- // ****************************************************************************************
- // Fields implementing LmsInterface
- // ****************************************************************************************
-
- @override
- String serverUrl = ''; // The Google REST endpoint
-
- // The user token is kept private (not in the interface).
- String? _userToken;
-
- @override
- String apiURL =
- 'https://classroom.googleapis.com/v1'; // Base URL for your Google Classroom
- @override
- String? userName;
- @override
- String? firstName;
- @override
- String? lastName;
- @override
- String? siteName = 'Google Classroom';
- @override
- String? fullName;
- @override
- String? profileImage;
- @override
- List? courses;
-
- late GoogleSignIn _googleSignIn;
-
- // ****************************************************************************************
- // Auth / Login
- // ****************************************************************************************
-
- @override
- Future login(String username, String password, String baseURL) {
- // TODO: implement google api code
- throw UnimplementedError();
- }
-
- Future loginOath(String clientID) async {
- print('Logging in to Google Classroom...');
-
- _googleSignIn = GoogleSignIn(
- clientId: clientID,
- scopes: [
- 'email',
- 'profile',
- 'https://www.googleapis.com/auth/classroom.courses',
- 'https://www.googleapis.com/auth/classroom.topics',
- 'https://www.googleapis.com/auth/classroom.rosters',
- 'https://www.googleapis.com/auth/classroom.coursework.students',
- 'https://www.googleapis.com/auth/classroom.coursework.me',
- 'https://www.googleapis.com/auth/classroom.courses.readonly',
- 'https://www.googleapis.com/auth/forms.body',
- 'https://www.googleapis.com/auth/forms.responses.readonly',
- 'https://www.googleapis.com/auth/classroom.courseworkmaterials.readonly',
- 'https://www.googleapis.com/auth/classroom.courseworkmaterials',
- 'https://www.googleapis.com/auth/forms.body.readonly',
- 'https://www.googleapis.com/auth/drive.file',
- ],
- );
-
- try {
- final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
- if (googleUser == null) {
- throw Exception("Google Sign-In was cancelled by the user.");
- }
-
- // Get the user's name
- userName = googleUser.email.split("@").first;
- fullName = googleUser.displayName ?? "Unknown User";
-
- List nameParts = fullName!.split(" ");
-
- firstName = nameParts.isNotEmpty ? nameParts.first : "";
- lastName = nameParts.length > 1 ? nameParts.sublist(1).join(" ") : "";
-
- print('Welcome, ${firstName ?? 'User'}');
-
- final GoogleSignInAuthentication googleAuth =
- await googleUser.authentication;
- _userToken = googleAuth.accessToken;
-
- if (_userToken == null) {
- throw Exception("Failed to obtain access token.");
- }
-
- LocalStorageService.saveGoogleAccessToken(_userToken!);
- } catch (error) {
- print("Google Sign-In Error: $error");
- throw Exception("Google Sign-In failed: $error");
- }
-
- courses = await getUserCourses();
-
- // for (Course course in courses!) {
- // print('teacherFolderId: ${course.teacherFolderId}');
- // }
- }
-
- @override
- bool isLoggedIn() {
- return _userToken != null;
- }
-
- String getGoogleAccessToken() {
- return _userToken!;
- }
-
- @override
- Future isUserTeacher(List moodleCourses) async {
- // TODO: implement google api code
- throw UnimplementedError();
- }
-
- @override
- void logout() {
- print('Logging out of Google...');
- _googleSignIn.signOut();
- resetLMSUserInfo();
- }
-
- @override
- void resetLMSUserInfo() {
- // Clear all user-related fields
- _userToken = null;
- apiURL = '';
- userName = null;
- firstName = null;
- lastName = null;
- siteName = null;
- fullName = null;
- profileImage = null;
- courses = [];
- }
-
- // ****************************************************************************************
- // Course-related methods
- // ****************************************************************************************
-
- @override
- Future> getCourses() async {
- // TODO: implement google api code
- // Never called??
- throw UnimplementedError();
- }
-
- @override
- Future> getUserCourses() async {
- if (_userToken == null) throw StateError('User not logged in to Google');
-
- final response = await ApiService().httpGet(
- Uri.parse('https://classroom.googleapis.com/v1/courses'),
- headers: {'Authorization': 'Bearer $_userToken'},
- );
-
- // TODO: remove after testing.
- // print('Google: ${response.body}');
-
- if (response.statusCode != 200) {
- throw HttpException(response.body);
- }
-
- final decodedJson = jsonDecode(response.body);
- List courses;
-
- // The response can be either a List or a Map with a 'courses' key
- if (decodedJson is List) {
- courses =
- decodedJson.map((i) => Course.empty().fromGoogleJson(i)).toList();
- } else if (decodedJson is Map) {
- final courseList = decodedJson['courses'] as List;
- courses =
- courseList.map((i) => Course.empty().fromGoogleJson(i)).toList();
- } else {
- throw StateError('Unexpected response format from Moodle');
- }
-
- // Optionally fetch quizzes/essays for each course
- for (Course course in courses) {
- // set topic ids
- final responseTopics = await ApiService().httpGet(
- Uri.parse(
- 'https://classroom.googleapis.com/v1/courses/${course.id}/topics/'),
- headers: {'Authorization': 'Bearer $_userToken'},
- );
-
- var decodedResponseTopics = jsonDecode(responseTopics.body);
-
- if (decodedResponseTopics.containsKey('topic')) {
- List topics = decodedResponseTopics["topic"];
-
- // Iterate and capture topicIds
- for (var topic in topics) {
- if (topic['name'] == 'Quiz') {
- course.quizTopicId = int.parse(topic['topicId']);
- // print('Id for quiz');
- // print(topic['topicId']);
- } else if (topic['name'] == 'Essay') {
- course.essayTopicId = int.parse(topic['topicId']);
- // print('Id for essay');
- // print(topic['topicId']);
- }
- }
- }
-
- course.quizzes = await getQuizzes(course.id, topicId: course.quizTopicId);
- // print('Quizzes for course ${course.id}: ${course.quizzes}');
- course.essays = await getEssays(course.id, topicId: course.essayTopicId);
- // print('Essays for course ${course.id}: ${course.essays}');
- }
-
- return courses;
- }
-
- @override
- Future> getCourseParticipants(String courseId) async {
- if (_userToken == null) {
- throw StateError('User not logged in to Google Classroom');
- }
-
- final List participants = [];
-
- // Fetch students
- final studentsResponse = await ApiService().httpGet(
- Uri.parse(apiURL + '/courses/$courseId/students'),
- headers: {'Authorization': 'Bearer $_userToken'},
- );
-
- if (studentsResponse.statusCode == 200) {
- final studentsJson = jsonDecode(studentsResponse.body);
- if (studentsJson.containsKey('students')) {
- for (var student in studentsJson['students']) {
- participants.add(Participant(
- id: student['userId']
- .hashCode, // Google Classroom does not provide numeric IDs
- fullname: student['profile']['name']['fullName'],
- firstname: student['profile']['name']['givenName'],
- lastname: student['profile']['name']['familyName'],
- roles: ['student'],
- ));
- }
- }
- } else {
- throw HttpException('Failed to fetch students: ${studentsResponse.body}');
- }
-
- // Fetch teachers
- final teachersResponse = await ApiService().httpGet(
- Uri.parse(apiURL + '/courses/$courseId/teachers'),
- headers: {'Authorization': 'Bearer $_userToken'},
- );
-
- if (teachersResponse.statusCode == 200) {
- final teachersJson = jsonDecode(teachersResponse.body);
- if (teachersJson.containsKey('teachers')) {
- for (var teacher in teachersJson['teachers']) {
- participants.add(Participant(
- id: teacher['userId'].hashCode,
- fullname: teacher['profile']['name']['fullName'],
- firstname: teacher['profile']['name']['givenName'],
- lastname: teacher['profile']['name']['familyName'],
- roles: ['teacher'],
- ));
- }
- }
- } else {
- throw HttpException('Failed to fetch teachers: ${teachersResponse.body}');
- }
-
- return participants;
- }
-
- // ****************************************************************************************
- // Quiz methods
- // ****************************************************************************************
-
- @override
- Future importQuiz(String courseid, String quizXml) async {
- // TODO: implement google api code
- throw UnimplementedError(
- 'This feature is not supported by Google Classroom. Please contact the developer.');
- }
-
- @override
- Future> getQuizzes(int? courseID, {int? topicId}) async {
- if (_userToken == null)
- throw StateError('User not logged in to Google Classroom');
-
- final response = await ApiService().httpGet(
- Uri.parse(
- 'https://classroom.googleapis.com/v1/courses/$courseID/courseWork'),
- headers: {'Authorization': 'Bearer $_userToken'},
- );
-
- // print('quizlist: ${response.body}');
-
- if (response.statusCode != 200) {
- throw HttpException(response.body);
- }
-
- // print('Getting quizzes from Google Classroom...');
- // print(response.body);
-
- final quizzesMap = jsonDecode(response.body) as Map;
- final decodedJson = quizzesMap['courseWork'] as List?;
-
- if (decodedJson == null) {
- return [];
- }
-
- List quizList = [];
- for (var item in decodedJson) {
- // print('Item: $item');
- // If courseID is null, return all quizzes; otherwise filter by course
- if (courseID == null || int.parse(item['courseId']) == courseID) {
- if (topicId != null && item.containsKey('topicId')) {
- if (int.parse(item['topicId']) == topicId) {
- quizList.add(Quiz.fromGoogleJson(item));
- }
- }
- }
- }
-
- // print('I am getting this quiz list: $quizList');
-
- return quizList;
- }
-
- @override
- Future createQuiz(String courseid, String quizname, String quizintro,
- String sectionid, String timeopen, String timeclose) async {
- print('Creating quiz in Google Classroom...');
- print('Course ID: $courseid');
- print('Quiz Name: $quizname');
- print('Quiz Intro: $quizintro');
- print('Section ID: $sectionid');
- print('Time Open: $timeopen');
- print('Time Close: $timeclose');
-
- try {
- // Convert timeopen to ISO 8601 format
- String formattedTimeOpen = DateTime.parse(timeopen).toIso8601String();
-
- String? assignmentId = await createAssignmentHelper(
- courseid, quizname, quizintro, sectionid, formattedTimeOpen);
-
- if (assignmentId != null) {
- return int.parse(assignmentId);
- } else {
- print('Failed to create quiz');
- return null;
- }
- } catch (e) {
- print('Error creating quiz: $e');
- return null;
- }
- }
-
- Future createAssignmentHelper(String courseId, String title,
- String description, String responderUri, String dueDate) async {
- print('Creating assignment in Google Classroom... Inside helper');
- print('Course ID: $courseId');
- print('Title: $title');
- print('Description: $description');
- print('Responder URI: $responderUri');
- print('Due Date: $dueDate');
-
- final token = _userToken;
- if (token == null) {
- print('User token is null');
- return null;
- }
-
- final url = Uri.parse(
- 'https://classroom.googleapis.com/v1/courses/$courseId/courseWork');
- final headers = {
- 'Authorization': 'Bearer $token',
- 'Content-Type': 'application/json',
- };
-
- // Parse the dueDate string
- DateTime parsedDate = DateTime.parse(dueDate);
-
- final body = jsonEncode({
- "title": title,
- "description": description,
- "workType": "ASSIGNMENT",
- "state": "PUBLISHED",
- "dueDate": {
- "year": parsedDate.year,
- "month": parsedDate.month,
- "day": parsedDate.day
- },
- "dueTime": {
- "hours": parsedDate.hour,
- "minutes": parsedDate.minute,
- "seconds": 0
- },
- "materials": [
- {
- "link": {"url": responderUri}
- }
- ]
- });
-
- // Print request details
- print('Request URL: $url');
- print('Request Headers: $headers');
- print('Request Body: $body');
-
- try {
- final response = await http.post(url, headers: headers, body: body);
-
- print("Response Status: ${response.statusCode}");
- print("Response Body: ${response.body}");
-
- if (response.statusCode == 200) {
- final data = jsonDecode(response.body);
- final assignmentId = data['id'];
- print('Assignment created successfully with ID: $assignmentId');
- return assignmentId;
- } else {
- print('Assignment creation failed: ${response.statusCode}');
- print('Error message: ${response.body}');
- return null;
- }
- } catch (e) {
- print('Error creating assignment: $e');
- return null;
- }
- }
-
- @override
- Future addRandomQuestions(
- String categoryid, String quizid, String numquestions) async {
- print('Adding random questions to quiz...');
- print('Category ID: $categoryid');
- // TODO: implement google api code
- throw UnimplementedError(
- 'This feature is not supported by Google Classroom. Please contact the developer.');
- }
-
- @override
- Future importQuizQuestions(String courseid, String quizXml) async {
- print('Importing quiz questions...');
- print('Course ID: $courseid');
- print('Quiz XML: $quizXml');
- // TODO: implement google api code
- throw UnimplementedError(
- 'This feature is not supported by Google Classroom. Please contact the developer.');
- }
-
- @override
- Future> getQuestionsFromQuiz(int quizId) async {
- // TODO: implement google api code
- throw UnimplementedError(
- 'This feature is not supported by Google Classroom. Please contact the developer.');
- }
-
- Future getAssignmentFormQuestions(
- String coursedId, String courseWorkId) async {
- print(
- 'Fetching form questions for course $coursedId, coursework $courseWorkId...');
- try {
- final accessToken = await _getToken();
- if (accessToken == null) {
- throw Exception('No access token found. Please log in again.');
- }
-
- final courseworkUrl =
- 'https://classroom.googleapis.com/v1/courses/$coursedId/courseWork/$courseWorkId';
- final courseworkResponse = await http.get(
- Uri.parse(courseworkUrl),
- headers: {
- 'Authorization': 'Bearer $accessToken',
- 'Content-Type': 'application/json',
- },
- );
-
- if (courseworkResponse.statusCode != 200) {
- throw Exception(
- 'Failed to fetch coursework: ${courseworkResponse.statusCode} - ${courseworkResponse.body}');
- }
-
- final courseworkData = jsonDecode(courseworkResponse.body);
- print('Coursework data: $courseworkData');
-
- String? formUrl;
- if (courseworkData['materials'] != null &&
- courseworkData['materials'].isNotEmpty) {
- for (var material in courseworkData['materials']) {
- if (material['link'] != null && material['link']['url'] != null) {
- final url = material['link']['url'];
- if (url.contains('docs.google.com/forms')) {
- formUrl = url;
- break;
- }
- }
- if (material['form'] != null && material['form']['formUrl'] != null) {
- final url = material['form']['formUrl'];
- if (url.contains('docs.google.com/forms')) {
- formUrl = url;
- break;
- }
- }
- }
- }
-
- if (formUrl == null) {
- throw Exception('No Google Form found in assignment materials.');
- }
- print('Extracted Form URL: $formUrl');
-
- // Extract and format dates to YYYY-MM-DD
- String? startDate;
- if (courseworkData['creationTime'] != null) {
- final dateTime = DateTime.parse(courseworkData['creationTime']);
- startDate =
- '${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.day.toString().padLeft(2, '0')}';
- }
-
- String? endDate;
- if (courseworkData['dueDate'] != null) {
- final dueDate = courseworkData['dueDate'];
- endDate =
- '${dueDate['year']}-${dueDate['month'].toString().padLeft(2, '0')}-${dueDate['day'].toString().padLeft(2, '0')}';
- }
-
- String? status = courseworkData['state'];
-
- final formId = await getFormIdFromViewformUrl(formUrl, accessToken);
- if (formId == null) {
- throw Exception('Failed to retrieve Form ID from viewform URL');
- }
- print('Form ID: $formId');
-
- final formsUrl = 'https://forms.googleapis.com/v1/forms/$formId';
- final formsResponse = await http.get(
- Uri.parse(formsUrl),
- headers: {
- 'Authorization': 'Bearer $accessToken',
- 'Content-Type': 'application/json',
- },
- );
-
- if (formsResponse.statusCode != 200) {
- throw Exception(
- 'Failed to fetch form: ${formsResponse.statusCode} - ${formsResponse.body}');
- }
-
- final formData = jsonDecode(formsResponse.body);
- print('Form data: $formData');
-
- String title = formData['info']?['title'] ?? 'Untitled Form';
- List questions = [];
- if (formData['items'] != null) {
- for (var item in formData['items']) {
- if (item['questionItem'] != null &&
- item['questionItem']['question'] != null) {
- String questionText = item['title'] ?? 'Unnamed Question';
- List options = [];
- if (item['questionItem']['question']['choiceQuestion'] != null) {
- var choiceQuestion =
- item['questionItem']['question']['choiceQuestion'];
- if (choiceQuestion['options'] != null) {
- for (var option in choiceQuestion['options']) {
- options.add(option['value'] ?? 'No Option');
- }
- }
- }
- questions
- .add(QuestionData(question: questionText, options: options));
- }
- }
- }
-
- return FormData(
- title: title,
- questions: questions,
- startDate: startDate,
- endDate: endDate,
- formUrl: formUrl,
- status: status,
- );
- } catch (e) {
- print('Error retrieving form questions: $e');
- return FormData(title: 'Error', questions: []);
- }
- }
-
- Future getFormIdFromViewformUrl(
- String viewformUrl, String accessToken) async {
- try {
- final publicKey = viewformUrl.split('/d/e/')[1].split('/')[0];
- print('Public key: $publicKey');
-
- final driveUrl = 'https://www.googleapis.com/drive/v3/files'
- '?q=mimeType="application/vnd.google-apps.form"'
- '&fields=files(id,name)'
- '&spaces=drive';
- final driveResponse = await http.get(
- Uri.parse(driveUrl),
- headers: {
- 'Authorization': 'Bearer $accessToken',
- 'Content-Type': 'application/json',
- },
- );
-
- if (driveResponse.statusCode != 200) {
- throw Exception('Failed to fetch Drive files: ${driveResponse.body}');
- }
-
- final driveData = jsonDecode(driveResponse.body);
- print('Drive data: $driveData');
-
- for (var file in driveData['files']) {
- final fileId = file['id'];
- print('Checking file ID: $fileId');
-
- final formsUrl = 'https://forms.googleapis.com/v1/forms/$fileId';
- final formResponse = await http.get(
- Uri.parse(formsUrl),
- headers: {
- 'Authorization': 'Bearer $accessToken',
- 'Content-Type': 'application/json',
- },
- );
-
- if (formResponse.statusCode == 200) {
- final formData = jsonDecode(formResponse.body);
- final responderUri = formData['responderUri'];
- print('Responder URI for $fileId: $responderUri');
-
- if (responderUri != null && responderUri.contains(publicKey)) {
- print('Matched form ID: $fileId');
- return fileId;
- }
- } else {
- print('Failed to fetch form $fileId: ${formResponse.statusCode}');
- }
- }
-
- throw Exception(
- 'No matching form found in Drive for the provided viewform URL');
- } catch (e) {
- print('Error fetching Form ID from Drive: $e');
- return null;
- }
- }
-
- Future _getToken() async {
- final token = LocalStorageService.getGoogleAccessToken();
- if (token == null) {
- print(
- 'Error: No valid OAuth token. Ensure the required scopes are enabled. Token null');
- }
- return token;
- }
-
- // ****************************************************************************************
- // Assignment methods
- // ****************************************************************************************
-
- @override
- Future> getEssays(int? courseID, {int? topicId}) async {
- if (_userToken == null)
- throw StateError('User not logged in to Google Classroom');
-
- final response = await ApiService().httpGet(
- Uri.parse(
- 'https://classroom.googleapis.com/v1/courses/$courseID/courseWork'),
- headers: {'Authorization': 'Bearer $_userToken'},
- );
-
- if (response.statusCode != 200) {
- throw HttpException(response.body);
- }
-
- final essaysMap = jsonDecode(response.body) as Map;
- final decodedJson = essaysMap['courseWork'] as List?;
-
- if (decodedJson == null) {
- return [];
- }
-
- List essayList = [];
- for (var item in decodedJson) {
- // If courseID is null, return all quizzes; otherwise filter by course
- if (courseID == null || int.parse(item['courseId']) == courseID) {
- if (topicId != null && item.containsKey('topicId')) {
- if (int.parse(item['topicId']) == topicId) {
- essayList.add(Assignment.empty().fromGoogleJson(item));
- }
- }
- }
- }
-
- return essayList;
- }
-
- @override
- Future?> createAssignment(
- String courseid,
- String sectionid,
- String assignmentName,
- String startdate,
- String enddate,
- String rubricJson,
- String description,
- ) async {
- print('Creating assignment...');
- print('Course ID: $courseid');
- print('Section ID: $sectionid');
- print('Assignment Name: $assignmentName');
- print('Start Date: $startdate');
- print('End Date: $enddate');
- // TODO: implement google api code
- throw UnimplementedError();
- }
-
- @override
- Future getContextId(int assignmentId, String courseId) async {
- print('Getting context ID...');
- print('Assignment ID: $assignmentId');
- print('Course ID: $courseId');
- // TODO: implement google api code
- throw UnimplementedError(
- 'This feature is not supported by Google Classroom. Please contact the developer.');
- }
-
- // ****************************************************************************************
- // Submissions and grading
- // ****************************************************************************************
-
- @override
- Future> getAssignmentSubmissions(int assignmentId) async {
- print('Getting assignment submissions...');
- print('Assignment ID: $assignmentId');
- // TODO: implement google api code
- throw UnimplementedError(
- 'This feature is not supported by Google Classroom. Please contact the developer.');
- }
-
- @override
- Future> getSubmissionsWithGrades(
- int assignmentId) async {
- print('Getting submissions with grades...');
- print('Assignment ID: $assignmentId');
-
- // TODO: implement google api code
- throw UnimplementedError(
- 'This feature is not supported by Google Classroom. Please contact the developer.');
- }
-
- @override
- Future getSubmissionStatus(
- int assignmentId, int userId) async {
- print('Getting submission status...');
- print('Assignment ID: $assignmentId');
- print('User ID: $userId');
- // TODO: implement google api code
- throw UnimplementedError(
- 'This feature is not supported by Google Classroom. Please contact the developer.');
- }
-
- @override
- Future> getAssignmentGrades(int assignmentId) async {
- print('Getting assignment grades...');
- print('Assignment ID: $assignmentId');
- // TODO: implement google api code
- throw UnimplementedError(
- 'This feature is not supported by Google Classroom. Please contact the developer.');
- }
-
- @override
- Future setRubricGrades(
- int assignmentId, int userId, String jsonGrades) async {
- print('Setting rubric grades...');
- print('Assignment ID: $assignmentId');
- print('User ID: $userId');
- // TODO: implement google api code
- throw UnimplementedError(
- 'This feature is not supported by Google Classroom. Please contact the developer.');
- }
-
- @override
- Future> getRubricGrades(int assignmentId, int userid) async {
- print('Getting rubric grades...');
- print('Assignment ID: $assignmentId');
- print('User ID: $userid');
- // TODO: implement google api code
- throw UnimplementedError(
- 'This feature is not supported by Google Classroom. Please contact the developer.');
- }
-
- @override
- Grade? findGradeForUser(List grades, int userId) {
- print('Finding grade for user...');
- print('User ID: $userId');
-
- // TODO: implement google api code
- throw UnimplementedError(
- 'This feature is not supported by Google Classroom. Please contact the developer.');
- }
-
- // ****************************************************************************************
- // Rubric retrieval
- // ****************************************************************************************
-
- @override
- Future getRubric(String assignmentid) async {
- // TODO: implement google api code
- print('Getting rubric...');
- print('Assignment ID: $assignmentid');
- throw UnimplementedError(
- 'This feature is not supported by Google Classroom. Please contact the developer.');
- }
- // ****************************************************************************************
- // Quiz creation and assignment with Answer Key
- //Short answer question 10 Points
- //Multiple choice question 10 Points
- //True/False question 5 Points
-
- // ****************************************************************************************
-
- Future createAndAssignQuizFromXml(
- String courseId,
- String quizName,
- String quizDescription,
- String quizAsXml,
- String dueDate,
- ) async {
- try {
- if (quizAsXml.isEmpty) {
- print('Error: quizAsXml is empty.');
- return false;
- }
-
- final document = xml.XmlDocument.parse(quizAsXml);
- final questions = document.findAllElements('question').toList();
- String? teacherFolderId;
-
- for (Course course in courses!) {
- if (course.id == int.parse(courseId)) {
- teacherFolderId = course.teacherFolderId;
- }
- }
-
- Map? formResponse =
- await _classroomApi.createForm(teacherFolderId, quizName);
- if (formResponse == null) {
- print('Error: Failed to create Google Form.');
- return false;
- }
-
- final String formId = formResponse['formId'];
- final String responderUri = formResponse['responderUri'];
-
- List> requests = [];
- requests.add({
- 'updateSettings': {
- 'settings': {
- 'emailCollectionType': 'DO_NOT_COLLECT',
- 'quizSettings': {'isQuiz': true}
- },
- 'updateMask': 'email_collection_type,quiz_settings',
- }
- });
-
- // Parse and add questions with answer keys and points
- for (var questionElement in questions) {
- String questionType = questionElement.getAttribute('type') ?? 'unknown';
- String questionText = questionElement
- .getElement('questiontext')
- ?.getElement('text')
- ?.text ??
- '';
-
- if (questionType == 'category') {
- print(
- 'Warning: Unsupported question type: $questionType. Skipping question.');
- continue;
- }
- switch (questionType) {
- case 'multichoice':
- int points = 10; // Set multichoice to 10 points
- List options = [];
- List correctAnswerIndices = [];
- var answerElements =
- questionElement.findAllElements('answer').toList();
- for (int i = 0; i < answerElements.length; i++) {
- var answerElement = answerElements[i];
- String optionText = answerElement.getElement('text')?.text ?? '';
- options.add(optionText);
- if (answerElement.getAttribute('fraction') == '100') {
- correctAnswerIndices.add(i);
- }
- }
- requests.add(_createMultipleChoiceQuestionRequest(
- questionText, options, correctAnswerIndices, points));
- break;
- case 'truefalse':
- int points = 5; // Set truefalse to 5 points
- String correctAnswer = questionElement
- .findAllElements('answer')
- .firstWhere((e) => e.getAttribute('fraction') == '100')
- .getElement('text')
- ?.text ??
- 'True';
- requests.add(_createTrueFalseQuestionRequest(
- questionText, correctAnswer, points));
- break;
- case 'shortanswer':
- int points = 10; // Set shortanswer to 10 point
- String correctAnswer = questionElement
- .findAllElements('answer')
- .first
- .getElement('text')
- ?.text ??
- '';
- requests.add(_createShortAnswerQuestionRequest(
- questionText, correctAnswer, points));
- break;
- default:
- print('Warning: Unsupported question type: $questionType');
- }
- }
-
- Map? batchResponse =
- await _classroomApi.batchUpdateForm(formId, requests);
- if (batchResponse == null) {
- print('Error: Failed to update Google Form.');
- return false;
- }
-
- String? assignmentId = await _classroomApi.createAssignment(
- courseId,
- quizName,
- quizDescription,
- responderUri,
- dueDate,
- );
-
- if (assignmentId == null) {
- print('Error: Failed to create Classroom assignment.');
- return false;
- }
-
- print(
- 'Quiz created and assigned successfully! Assignment ID: $assignmentId');
- return true;
- } catch (e) {
- print('Error during quiz creation and assignment: $e');
- return false;
- }
- }
-
- Map _createMultipleChoiceQuestionRequest(String questionText,
- List options, List correctAnswerIndices, int points) {
- List> choices = [];
- for (String option in options) {
- choices.add({'value': option});
- }
-
- return {
- 'createItem': {
- 'item': {
- 'title': questionText,
- 'questionItem': {
- 'question': {
- 'required': true,
- 'choiceQuestion': {
- 'type': 'RADIO',
- 'options': choices,
- },
- 'grading': {
- 'pointValue': points,
- 'correctAnswers': {
- 'answers': correctAnswerIndices
- .map((index) => {'value': options[index]})
- .toList(),
- },
- },
- },
- },
- },
- 'location': {'index': 0},
- },
- };
- }
-
- Map _createTrueFalseQuestionRequest(
- String questionText, String correctAnswer, int points) {
- List options = ["True", "False"];
- return {
- 'createItem': {
- 'item': {
- 'title': questionText,
- 'questionItem': {
- 'question': {
- 'required': true,
- 'choiceQuestion': {
- 'type': 'RADIO',
- 'options': [
- {'value': 'True'},
- {'value': 'False'},
- ],
- },
- 'grading': {
- 'pointValue': points,
- 'correctAnswers': {
- 'answers': [
- {'value': correctAnswer},
- ],
- },
- },
- },
- },
- },
- 'location': {'index': 0},
- },
- };
- }
-
- Map _createShortAnswerQuestionRequest(
- String questionText, String correctAnswer, int points) {
- return {
- 'createItem': {
- 'item': {
- 'title': questionText,
- 'questionItem': {
- 'question': {
- 'required': true,
- 'textQuestion': {},
- 'grading': {
- 'pointValue': points,
- 'correctAnswers': {
- 'answers': [
- {'value': correctAnswer},
- ],
- },
- },
- },
- },
- },
- 'location': {'index': 0},
- },
- };
- }
-
- @override
- Future> getQuizGradesForParticipants(
- String courseId, int quizId) {
- // TODO: implement getQuizGradesForParticipants
- throw UnimplementedError();
- }
-}
diff --git a/team_a/teamA/lib/Api/lms/lms_interface.dart b/team_a/teamA/lib/Api/lms/lms_interface.dart
deleted file mode 100644
index 28a932c6..00000000
--- a/team_a/teamA/lib/Api/lms/lms_interface.dart
+++ /dev/null
@@ -1,66 +0,0 @@
-import 'package:learninglens_app/beans/course.dart';
-import 'package:learninglens_app/beans/quiz.dart';
-import 'package:learninglens_app/beans/quiz_type.dart';
-import 'package:learninglens_app/beans/assignment.dart';
-import 'package:learninglens_app/beans/participant.dart';
-import 'package:learninglens_app/beans/submission_status.dart';
-import 'package:learninglens_app/beans/grade.dart';
-import 'package:learninglens_app/beans/submission.dart';
-import 'package:learninglens_app/beans/submission_with_grade.dart';
-import 'package:learninglens_app/beans/moodle_rubric.dart';
-
-// Singleton interface class for API access.
-abstract class LmsInterface {
- late String serverUrl;
-
- // User info
- late String apiURL;
- String? userName;
- String? firstName;
- String? lastName;
- String? siteName;
- String? fullName;
- String? profileImage;
- List? courses;
-
- // Authentication/Login methods
- Future login(String username, String password, String baseURL);
- bool isLoggedIn();
- Future isUserTeacher(List moodleCourses);
- void logout();
- void resetLMSUserInfo();
-
- // Course methods
- Future> getCourses();
- Future> getUserCourses();
- Future> getCourseParticipants(String courseId);
-
- // Quiz methods
- Future importQuiz(String courseid, String quizXml);
- Future> getQuizzes(int? courseID, {int? topicId});
- Future createQuiz(String courseid, String quizname, String quizintro,
- String sectionid, String timeopen, String timeclose);
- Future addRandomQuestions(
- String categoryid, String quizid, String numquestions);
- Future importQuizQuestions(String courseid, String quizXml);
- Future> getQuestionsFromQuiz(int quizId);
-
- // Assignment methods
- Future> getEssays(int? courseID, {int? topicId});
- Future?> createAssignment(String courseid, String sectionid, String assignmentName,
- String startdate, String enddate, String rubricJson, String description);
- Future getContextId(int assignmentId, String courseId);
-
- // Submission and grading methods
- Future> getAssignmentSubmissions(int assignmentId);
- Future> getSubmissionsWithGrades(int assignmentId);
- Future getSubmissionStatus(int assignmentId, int userId);
- Future> getAssignmentGrades(int assignmentId);
- Future setRubricGrades(int assignmentId, int userId, String jsonGrades);
- Future> getRubricGrades(int assignmentId, int userid);
- Grade? findGradeForUser(List grades, int userId);
-
- // Rubric methods
- Future getRubric(String assignmentid);
- Future> getQuizGradesForParticipants(String courseId, int quizId);
-}
diff --git a/team_a/teamA/lib/Api/lms/moodle/classroom_service.dart b/team_a/teamA/lib/Api/lms/moodle/classroom_service.dart
deleted file mode 100644
index b9f78bb0..00000000
--- a/team_a/teamA/lib/Api/lms/moodle/classroom_service.dart
+++ /dev/null
@@ -1 +0,0 @@
-// TODO Implement this library.
\ No newline at end of file
diff --git a/team_a/teamA/lib/Api/lms/moodle/moodle_lms_service.dart b/team_a/teamA/lib/Api/lms/moodle/moodle_lms_service.dart
deleted file mode 100644
index 2f04ecbb..00000000
--- a/team_a/teamA/lib/Api/lms/moodle/moodle_lms_service.dart
+++ /dev/null
@@ -1,1298 +0,0 @@
-import 'dart:convert';
-import 'dart:io';
-import 'package:learninglens_app/Api/lms/lms_interface.dart';
-import 'package:learninglens_app/beans/course.dart';
-import 'package:learninglens_app/beans/lesson_plan.dart';
-import 'package:learninglens_app/beans/question_stat_type.dart';
-import 'package:learninglens_app/beans/quiz.dart';
-import 'package:learninglens_app/beans/override.dart';
-import 'package:learninglens_app/beans/assignment.dart';
-import 'package:learninglens_app/beans/participant.dart';
-import 'package:learninglens_app/beans/quiz_override';
-import 'package:learninglens_app/beans/quiz_type.dart';
-import 'package:learninglens_app/beans/submission_status.dart';
-import 'package:learninglens_app/beans/grade.dart';
-import 'package:learninglens_app/beans/submission.dart';
-import 'package:learninglens_app/beans/submission_with_grade.dart';
-import 'package:learninglens_app/beans/moodle_rubric.dart';
-import 'package:learninglens_app/services/api_service.dart';
-
-/// A Singleton class for Moodle API access implementing [LmsInterface].
-class MoodleLmsService implements LmsInterface {
- // ****************************************************************************************
- // Static / Singleton internals
- // ****************************************************************************************
-
- static final MoodleLmsService _instance = MoodleLmsService._internal();
-
- /// The singleton accessor.
- factory MoodleLmsService() => _instance;
-
- /// Private named constructor.
- MoodleLmsService._internal();
-
- // ****************************************************************************************
- // Fields implementing ApiSingletonInterface
- // ****************************************************************************************
-
- @override
- String serverUrl = '/webservice/rest/server.php'; // The Moodle REST endpoint
-
- // The user token is kept private (not in the interface).
- String? _userToken;
-
- @override
- String apiURL =
- ''; // Base URL for your Moodle instance, e.g. "https://yourmoodle.com"
- @override
- String? userName;
- @override
- String? firstName;
- @override
- String? lastName;
- @override
- String? siteName;
- @override
- String? fullName;
- @override
- String? profileImage;
- @override
- List? courses;
-
- List? overrides;
-
- // ****************************************************************************************
- // Auth / Login
- // ****************************************************************************************
-
- @override
- Future login(String username, String password, String baseURL) async {
- print('Logging in to Moodle...');
-
- // 1) Obtain the token by calling Moodle's login/token.php
- final response = await ApiService().httpGet(Uri.parse(
- '$baseURL/login/token.php?username=$username&password=$password&service=moodle_mobile_app'));
-
- if (response.statusCode != 200) {
- throw HttpException(response.body);
- }
-
- final data = jsonDecode(response.body) as Map;
- if (data.containsKey('error')) {
- throw HttpException(data['error']);
- }
-
- // Store token locally
- _userToken = data['token'];
- apiURL = baseURL;
-
- // 2) Retrieve user info
- final userinforesponse =
- await ApiService().httpPost(Uri.parse(apiURL + serverUrl), body: {
- 'wstoken': _userToken,
- 'wsfunction': 'core_webservice_get_site_info',
- 'moodlewsrestformat': 'json',
- });
-
- if (userinforesponse.statusCode != 200) {
- throw HttpException(userinforesponse.body);
- }
-
- // 3) Parse user info
- final userData = jsonDecode(userinforesponse.body) as Map;
- userName = userData['username'];
- firstName = userData['firstname'];
- lastName = userData['lastname'];
- siteName = userData['sitename'];
- fullName = userData['fullname'];
- profileImage = userData['userpictureurl'];
-
- // 4) Optionally load user courses right away
- courses = await getUserCourses();
- overrides = await getAssignmentOverrides();
- }
-
- @override
- bool isLoggedIn() {
- return _userToken != null;
- }
-
- @override
- Future isUserTeacher(List moodleCourses) async {
- // Check each course to see if the user has a teacher role
- for (var course in moodleCourses) {
- final rolesResponse =
- await ApiService().httpPost(Uri.parse(apiURL + serverUrl), body: {
- 'wstoken': _userToken,
- 'wsfunction': 'core_enrol_get_enrolled_users',
- 'courseid': course.id.toString(),
- 'moodlewsrestformat': 'json',
- });
-
- if (rolesResponse.statusCode != 200) {
- throw Exception('Failed to load roles for course ${course.id}');
- }
-
- // If the user has roleid == 3 or 4, they are teacher-like roles
- final users = jsonDecode(rolesResponse.body) as List;
- for (var user in users) {
- if (user['username'].toString() == userName) {
- for (var role in user['roles']) {
- if (role['roleid'] == 3 || role['roleid'] == 4) {
- return true;
- }
- }
- }
- }
- }
- return false;
- }
-
- @override
- void logout() {
- print('Logging out of Moodle...');
- resetLMSUserInfo();
- }
-
- @override
- void resetLMSUserInfo() {
- // Clear all user-related fields
- _userToken = null;
- apiURL = '';
- userName = null;
- firstName = null;
- lastName = null;
- siteName = null;
- fullName = null;
- profileImage = null;
- courses = [];
- }
-
- // ****************************************************************************************
- // Course-related methods
- // ****************************************************************************************
-
- @override
- Future> getCourses() async {
- // Returns all courses on the Moodle site (requires admin or special permissions)
- if (_userToken == null) throw StateError('User not logged in to Moodle');
-
- final response =
- await ApiService().httpPost(Uri.parse(apiURL + serverUrl), body: {
- 'wstoken': _userToken,
- 'wsfunction': 'core_course_get_courses',
- 'moodlewsrestformat': 'json',
- });
-
- if (response.statusCode != 200) {
- throw HttpException(response.body);
- }
-
- final listData = jsonDecode(response.body) as List;
- return listData.map((i) => Course.empty().fromMoodleJson(i)).toList();
- }
-
- @override
- Future> getUserCourses() async {
- // Returns courses the user is enrolled in
- if (_userToken == null) throw StateError('User not logged in to Moodle');
-
- final response =
- await ApiService().httpPost(Uri.parse(apiURL + serverUrl), body: {
- 'wstoken': _userToken,
- 'wsfunction':
- 'core_course_get_enrolled_courses_by_timeline_classification',
- 'classification': 'inprogress',
- 'moodlewsrestformat': 'json',
- });
-
- if (response.statusCode != 200) {
- throw HttpException(response.body);
- }
-
- final decodedJson = jsonDecode(response.body);
- List userCourses;
-
- // The response can be either a List or a Map with a 'courses' key
- if (decodedJson is List) {
- userCourses =
- decodedJson.map((i) => Course.empty().fromMoodleJson(i)).toList();
- } else if (decodedJson is Map) {
- final courseList = decodedJson['courses'] as List;
- userCourses =
- courseList.map((i) => Course.empty().fromMoodleJson(i)).toList();
- } else {
- throw StateError('Unexpected response format from Moodle');
- }
-
- // Optionally fetch quizzes/essays for each course
- for (Course c in userCourses) {
- c.quizzes = await getQuizzes(c.id);
- c.essays = await getEssays(c.id);
- }
- return userCourses;
- }
-
- Future> getAssignmentOverrides() async {
- // Returns courses the user is enrolled in
- if (_userToken == null) throw StateError('User not logged in to Moodle');
-
- final response =
- await ApiService().httpPost(Uri.parse(apiURL + serverUrl), body: {
- 'wstoken': _userToken,
- 'wsfunction': 'local_learninglens_get_all_overrides',
- 'moodlewsrestformat': 'json',
- });
-
- if (response.statusCode != 200) {
- throw HttpException(response.body);
- }
-
- final decodedJson = jsonDecode(response.body);
- List assignmentOverrides;
-
- if (decodedJson is List) {
- assignmentOverrides =
- decodedJson.map((i) => Override.empty().fromMoodleJson(i)).toList();
- } else if (decodedJson is Map) {
- final courseList = decodedJson['courses'] as List;
- assignmentOverrides =
- courseList.map((i) => Override.empty().fromMoodleJson(i)).toList();
- } else {
- throw StateError('Unexpected response format from Moodle');
- }
-
- return assignmentOverrides;
- }
-
- @override
- Future> getCourseParticipants(String courseId) async {
- if (_userToken == null) throw StateError('User not logged in to Moodle');
-
- final response =
- await ApiService().httpPost(Uri.parse(apiURL + serverUrl), body: {
- 'wstoken': _userToken,
- 'wsfunction': 'core_enrol_get_enrolled_users',
- 'courseid': courseId,
- 'moodlewsrestformat': 'json',
- });
-
- if (response.statusCode != 200) {
- throw HttpException(response.body);
- }
-
- final decodedJson = jsonDecode(response.body);
- List participants = [];
- if (decodedJson is List) {
- for (var participant in decodedJson) {
- if (isStudent(participant)) {
- participants.add(Participant.empty().fromMoodleJson(participant));
- }
- }
- return participants;
- // return decodedJson
- // .map((i) => Participant.empty().fromMoodleJson(i))
- // .toList();
- } else {
- throw StateError('Unexpected response format (expected a List)');
- }
- }
-
- bool isStudent(Map json) {
- // Moodle student role id equals 5
- bool isStudent = false;
- for (Map role in json['roles']) {
- if (role['roleid'] == 5) {
- isStudent = true;
- }
- }
- return isStudent;
- }
-
- // ****************************************************************************************
- // Quiz methods
- // ****************************************************************************************
-
- @override
- Future importQuiz(String courseid, String quizXml) async {
- if (_userToken == null) throw StateError('User not logged in to Moodle');
-
- final response = await ApiService().httpPost(
- Uri.parse(apiURL + serverUrl),
- body: {
- 'wstoken': _userToken,
- 'wsfunction': 'local_quizgen_import_questions',
- 'moodlewsrestformat': 'json',
- 'courseid': courseid,
- 'questionxml': quizXml,
- },
- );
-
- if (response.statusCode != 200) {
- throw HttpException(response.body);
- }
- // Check if Moodle returned an 'error' inside the JSON string
- if (response.body.contains('error')) {
- throw HttpException(response.body);
- }
- }
-
- @override
- Future> getQuizzes(int? courseID, {int? topicId}) async {
- if (_userToken == null) throw StateError('User not logged in to Moodle');
-
- final response = await ApiService().httpPost(
- Uri.parse(apiURL + serverUrl),
- body: {
- 'wstoken': _userToken,
- 'wsfunction': 'mod_quiz_get_quizzes_by_courses',
- 'moodlewsrestformat': 'json',
- },
- );
-
- if (response.statusCode != 200) {
- throw HttpException(response.body);
- }
-
- final quizzesMap = jsonDecode(response.body) as Map;
- final decodedJson = quizzesMap['quizzes'] as List?;
-
- if (decodedJson == null) {
- return [];
- }
-
- List quizList = [];
- for (var item in decodedJson) {
- // If courseID is null, return all quizzes; otherwise filter by course
- if (courseID == null || item['course'] == courseID) {
- quizList.add(Quiz(
- name: item['name'],
- coursedId: item['course'],
- description: item['intro'],
- id: item['id'],
- timeOpen: item['timeopen'] =
- DateTime.fromMillisecondsSinceEpoch(item['timeopen'] * 1000),
- timeClose: item['timeclose'] =
- DateTime.fromMillisecondsSinceEpoch(item['timeclose'] * 1000)));
- }
- }
- return quizList;
- }
-
- @override
- Future createQuiz(String courseid, String quizname, String quizintro,
- String sectionid, String timeopen, String timeclose) async {
- if (_userToken == null) throw StateError('User not logged in to Moodle');
-
- try {
- final response = await ApiService().httpPost(
- Uri.parse(apiURL + serverUrl),
- body: {
- 'wstoken': _userToken,
- 'wsfunction': 'local_learninglens_create_quiz',
- 'moodlewsrestformat': 'json',
- 'courseid': courseid,
- 'name': quizname,
- 'intro': quizintro,
- 'sectionid': sectionid,
- 'timeopen': timeopen,
- 'timeclose': timeclose,
- },
- );
-
- if (response.statusCode != 200) {
- print('Failed to create quiz. Status code: ${response.statusCode}.');
- return null;
- }
-
- final responseData = jsonDecode(response.body) as Map;
- print('Create Quiz Response: $responseData');
-
- return responseData['quizid'];
- } catch (e) {
- print('Error creating quiz: $e');
- return null;
- }
- }
-
- @override
- Future addRandomQuestions(
- String categoryid, String quizid, String numquestions) async {
- if (_userToken == null) throw StateError('User not logged in to Moodle');
-
- final response = await ApiService().httpPost(
- Uri.parse(apiURL + serverUrl),
- body: {
- 'wstoken': _userToken,
- 'wsfunction': 'local_learninglens_add_type_randoms_to_quiz',
- 'moodlewsrestformat': 'json',
- 'categoryid': categoryid,
- 'quizid': quizid,
- 'numquestions': numquestions,
- },
- );
-
- if (response.statusCode != 200) {
- return 'Request failed with status: ${response.statusCode}.';
- }
-
- // The response may be a simple boolean string or a JSON object
- try {
- if (response.body == 'true' || response.body == 'false') {
- final boolString = (response.body == 'true').toString();
- print('Boolean Response: $boolString');
- return boolString;
- } else {
- final responseData = jsonDecode(response.body) as Map;
- print('Response: $responseData');
- return responseData['status'];
- }
- } catch (e) {
- print('Error parsing response in addRandomQuestions: $e');
- return e.toString();
- }
- }
-
- @override
- Future importQuizQuestions(String courseid, String quizXml) async {
- if (_userToken == null) throw StateError('User not logged in to Moodle');
-
- try {
- final response = await ApiService().httpPost(
- Uri.parse(apiURL + serverUrl),
- body: {
- 'wstoken': _userToken,
- 'wsfunction': 'local_learninglens_import_questions',
- 'moodlewsrestformat': 'json',
- 'courseid': courseid,
- 'questionxml': quizXml,
- },
- );
-
- if (response.statusCode != 200) {
- print('Request failed with status: ${response.statusCode}.');
- return null;
- }
-
- // Some Moodle plugins return extra text before JSON; parse from first '{'
- final int indexOfBrace = response.body.indexOf('{');
- if (indexOfBrace == -1) {
- print('No JSON object found in response for importQuizQuestions.');
- return null;
- }
- final jsonPart = response.body.substring(indexOfBrace);
- final responseData = jsonDecode(jsonPart) as Map;
- print('Import Questions Response: $responseData');
-
- return responseData['categoryid'] as int?;
- } catch (e) {
- print('Error importing quiz questions: $e');
- return null;
- }
- }
-
- // ****************************************************************************************
- // Assignment methods
- // ****************************************************************************************
-
- @override
- Future> getEssays(int? courseID, {int? topicId}) async {
- if (_userToken == null) throw StateError('User not logged in to Moodle');
-
- final response = await ApiService().httpPost(
- Uri.parse(apiURL + serverUrl),
- body: {
- 'wstoken': _userToken,
- 'wsfunction': 'mod_assign_get_assignments',
- 'moodlewsrestformat': 'json',
- },
- );
-
- if (response.statusCode != 200) {
- throw HttpException(response.body);
- }
-
- final mapJson = jsonDecode(response.body) as Map;
- final coursesList = mapJson['courses'] as List?;
-
- if (coursesList == null) {
- return [];
- }
-
- final results = [];
- for (var cItem in coursesList) {
- // If courseID is null, get for all courses; if not, filter
- if (courseID == null || cItem['id'] == courseID) {
- final assignmentList = cItem['assignments'] as List;
- for (var a in assignmentList) {
- results.add(Assignment.empty().fromMoodleJson(a));
- }
- }
- }
- return results;
- }
-
- @override
- Future?> createAssignment(
- String courseid,
- String sectionid,
- String assignmentName,
- String startdate,
- String enddate,
- String rubricJson,
- String description,
- ) async {
- if (_userToken == null) throw StateError('User not logged in to Moodle');
-
- try {
- final response = await ApiService().httpPost(
- Uri.parse(apiURL + serverUrl),
- body: {
- 'wstoken': _userToken,
- 'wsfunction': 'local_learninglens_create_assignment',
- 'moodlewsrestformat': 'json',
- 'courseid': courseid,
- 'sectionid': sectionid,
- 'assignmentName': assignmentName,
- 'startdate': startdate,
- 'enddate': enddate,
- 'rubricJson': rubricJson,
- 'description': description,
- },
- );
-
- if (response.statusCode != 200) {
- print('Request failed with status: ${response.statusCode}.');
- return null;
- }
-
- final responseData = jsonDecode(response.body) as Map;
- print('Create Assignment Response: $responseData');
-
- return responseData;
- } catch (e) {
- print('Error occurred while creating assignment: $e');
- return null;
- }
- }
-
- @override
- Future getContextId(int assignmentId, String courseId) async {
- if (_userToken == null) throw StateError('User not logged in to Moodle');
-
- try {
- final response = await ApiService().httpPost(
- Uri.parse(apiURL + serverUrl),
- body: {
- 'wstoken': _userToken,
- 'wsfunction': 'core_course_get_contents',
- 'moodlewsrestformat': 'json',
- 'courseid': courseId,
- },
- );
-
- if (response.statusCode != 200) {
- print(
- 'Failed to fetch course contents. Status code: ${response.statusCode}');
- return null;
- }
-
- final data = jsonDecode(response.body) as List;
- for (var section in data) {
- final modules = section['modules'] as List;
- for (var module in modules) {
- // Look for an assignment module with matching instance
- if (module['instance'] == assignmentId &&
- module['modname'] == 'assign') {
- final contextId = module['contextid'];
- print('Context ID for assignment $assignmentId is $contextId');
- return contextId as int?;
- }
- }
- }
- return null;
- } catch (e, st) {
- print('Error fetching context ID: $e');
- print('StackTrace: $st');
- return null;
- }
- }
-
- // ****************************************************************************************
- // Submissions and grading
- // ****************************************************************************************
-
- @override
- Future> getAssignmentSubmissions(int assignmentId) async {
- if (_userToken == null) throw StateError('User not logged in to Moodle');
-
- try {
- final response = await ApiService().httpPost(
- Uri.parse(apiURL + serverUrl),
- body: {
- 'wstoken': _userToken,
- 'wsfunction': 'mod_assign_get_submissions',
- 'moodlewsrestformat': 'json',
- 'assignmentids[0]': assignmentId.toString(),
- 'status': 'submitted',
- },
- );
-
- if (response.statusCode != 200) {
- print(
- 'Failed to load submissions. Status code: ${response.statusCode}');
- return [];
- }
-
- final data = jsonDecode(response.body) as Map;
- if (data.containsKey('exception')) {
- throw Exception('Moodle API Error: ${data['message']}');
- }
-
- // Debugging
- print('Full Response Data (Submissions): ${jsonEncode(data)}');
-
- final assignmentsArr = data['assignments'] as List? ?? [];
- final submissionsData = >[];
-
- // Extract each "submission" plus the assignmentId
- for (var assign in assignmentsArr) {
- final subs = assign['submissions'] as List? ?? [];
- for (var sub in subs) {
- submissionsData.add({
- 'assignmentid': assign['assignmentid'],
- 'submission': sub,
- });
- }
- }
-
- if (submissionsData.isEmpty) {
- print('No submissions found for assignment $assignmentId');
- return [];
- }
-
- return submissionsData
- .map((jsonMap) => Submission.empty().fromMoodleJson(jsonMap))
- .toList();
- } catch (e, st) {
- print('Error fetching submissions: $e');
- print('StackTrace: $st');
- return [];
- }
- }
-
- @override
- Future> getSubmissionsWithGrades(
- int assignmentId) async {
- // Combine submissions with their associated grade
- final submissions = await getAssignmentSubmissions(assignmentId);
- final grades = await getAssignmentGrades(assignmentId);
-
- final results = [];
- for (final submission in submissions) {
- final match = findGradeForUser(grades, submission.userid);
- results.add(SubmissionWithGrade(submission: submission, grade: match));
- }
- return results;
- }
-
- @override
- Future getSubmissionStatus(
- int assignmentId, int userId) async {
- if (_userToken == null) throw StateError('User not logged in to Moodle');
-
- try {
- final response = await ApiService().httpPost(
- Uri.parse(apiURL + serverUrl),
- body: {
- 'wstoken': _userToken,
- 'wsfunction': 'mod_assign_get_submission_status',
- 'moodlewsrestformat': 'json',
- 'assignid': assignmentId.toString(),
- 'userid': userId.toString(),
- },
- );
-
- if (response.statusCode != 200) {
- print(
- 'Failed to load submission status. Status code: ${response.statusCode}');
- return null;
- }
-
- final data = jsonDecode(response.body) as Map;
- if (data.containsKey('exception')) {
- throw Exception('Moodle API Error: ${data['message']}');
- }
-
- return SubmissionStatus.empty().fromMoodleJson(data);
- } catch (e, st) {
- print('Error fetching submission status: $e');
- print('StackTrace: $st');
- return null;
- }
- }
-
- @override
- Future> getAssignmentGrades(int assignmentId) async {
- if (_userToken == null) throw StateError('User not logged in to Moodle');
-
- try {
- final response = await ApiService().httpPost(
- Uri.parse(apiURL + serverUrl),
- body: {
- 'wstoken': _userToken,
- 'wsfunction': 'mod_assign_get_grades',
- 'moodlewsrestformat': 'json',
- 'assignmentids[0]': assignmentId.toString(),
- },
- );
-
- if (response.statusCode != 200) {
- print(
- 'Failed to load assignment grades. Status code: ${response.statusCode}');
- return [];
- }
-
- final data = jsonDecode(response.body) as Map;
- if (data.containsKey('exception')) {
- throw Exception('Moodle API Error: ${data['message']}');
- }
-
- print('Grades Response Data: ${jsonEncode(data)}');
-
- final grades = [];
- final assignmentsList = data['assignments'] as List? ?? [];
- for (var assignment in assignmentsList) {
- final gList = assignment['grades'] as List? ?? [];
- for (var g in gList) {
- grades.add(Grade.empty().fromMoodleJson(g));
- }
- }
- return grades;
- } catch (e, st) {
- print('Error fetching assignment grades: $e');
- print('StackTrace: $st');
- return [];
- }
- }
-
- @override
- Future setRubricGrades(
- int assignmentId, int userId, String jsonGrades) async {
- if (_userToken == null) throw StateError('User not logged in to Moodle');
-
- try {
- final response = await ApiService().httpPost(
- Uri.parse(apiURL + serverUrl),
- body: {
- 'wstoken': _userToken,
- 'wsfunction': 'local_learninglens_write_rubric_grades',
- 'moodlewsrestformat': 'json',
- 'assignmentid': assignmentId.toString(),
- 'userid': userId.toString(),
- 'rubricgrades': jsonGrades,
- },
- );
-
- if (response.statusCode != 200) {
- print(
- 'Failed to set rubric grades. Status code: ${response.statusCode}');
- return false;
- }
- return true;
- } catch (e, st) {
- print('Error setting rubric grades: $e');
- print('StackTrace: $st');
- return false;
- }
- }
-
- @override
- Future> getRubricGrades(int assignmentId, int userid) async {
- if (_userToken == null) throw StateError('User not logged in to Moodle');
- try {
- final response = await ApiService().httpPost(
- Uri.parse(apiURL + serverUrl),
- body: {
- 'wstoken': _userToken,
- 'wsfunction': 'local_learninglens_get_rubric_grades',
- 'moodlewsrestformat': 'json',
- 'assignmentid': assignmentId.toString(),
- 'userid': userid.toString(),
- },
- );
-
- if (response.statusCode != 200) {
- print('Failed to load rubric grades. Status: ${response.statusCode}');
- return [];
- }
-
- final data = jsonDecode(response.body) as List;
- if (data.isEmpty || data.first is! Map) {
- return [];
- }
- print('Rubric Grades Response: ${jsonEncode(data)}');
- return data;
- } catch (e, st) {
- print('Error fetching rubric grades: $e');
- print('StackTrace: $st');
- return [];
- }
- }
-
- @override
- Grade? findGradeForUser(List grades, int userId) {
- for (final grade in grades) {
- if (grade.userid == userId) {
- return grade;
- }
- }
- return null;
- }
-
- // ****************************************************************************************
- // Rubric retrieval
- // ****************************************************************************************
-
- @override
- Future getRubric(String assignmentid) async {
- if (_userToken == null) throw StateError('User not logged in to Moodle');
-
- final response = await ApiService().httpPost(
- Uri.parse(apiURL + serverUrl),
- body: {
- 'wstoken': _userToken,
- 'wsfunction': 'local_learninglens_get_rubric',
- 'moodlewsrestformat': 'json',
- 'assignmentid': assignmentid,
- },
- );
-
- if (response.statusCode != 200) {
- print('Request failed with status: ${response.statusCode}.');
- return null;
- }
-
- final List responseData = jsonDecode(response.body);
- if (responseData.isEmpty || responseData.first is! Map) {
- return null;
- }
-
- print('Rubric JSON Response: $responseData');
- return MoodleRubric.empty().fromMoodleJson(responseData.first);
- }
-
- // ****************************************************************************************
- // TODO: add the method below to the lms_interface.
- // ****************************************************************************************
- /**
- * Fetches all the questions from a quiz.
- */
- Future> getQuestionsFromQuiz(int quizId) async {
- if (_userToken == null) throw StateError('User not logged in to Moodle');
-
- final url = Uri.parse('$apiURL$serverUrl');
-
- final response = await ApiService().httpPost(
- url,
- body: {
- 'wstoken': _userToken!,
- 'wsfunction': 'local_learninglens_get_questions_from_quiz',
- 'moodlewsrestformat': 'json',
- 'quizid': quizId.toString(),
- },
- );
-
- if (response.statusCode == 200) {
- final List jsonList = json.decode(response.body);
-
- return jsonList
- .map((json) => QuestionType.empty().fromMoodleJson(json))
- .toList();
- } else {
- throw Exception("Failed to fetch questions: ${response.body}");
- }
- }
-
- Future addQuizOverride({
- required int quizId,
- int? userId,
- int? groupId,
- int? timeOpen,
- int? timeClose,
- int? timeLimit,
- int? attempts,
- String? password,
- }) async {
- if (_userToken == null) throw StateError('User not logged in to Moodle');
-
- final url = Uri.parse('$apiURL$serverUrl');
-
- // Dynamically build request body, removing null values
- final Map body = {
- 'wstoken': _userToken!,
- 'wsfunction': 'local_learninglens_add_quiz_override',
- 'moodlewsrestformat': 'json',
- 'quizid': quizId.toString(),
- };
-
- // Add only non-null fields
- if (userId != null) body['userid'] = userId.toString();
- if (groupId != null) body['groupid'] = groupId.toString();
- if (timeOpen != null) body['timeopen'] = timeOpen.toString();
- if (timeClose != null) body['timeclose'] = timeClose.toString();
- if (timeLimit != null) body['timelimit'] = timeLimit.toString();
- if (attempts != null) body['attempts'] = attempts.toString();
- if (password != null && password.isNotEmpty) body['password'] = password;
-
- final response = await ApiService().httpPost(url, body: body);
-
- final responseData = json.decode(response.body);
-
- if (response.statusCode == 200 && responseData is Map) {
- return QuizOverride.empty().fromMoodleJson(responseData);
- } else {
- throw Exception("Failed to create quiz override: ${response.body}");
- }
- }
-
- Future addEssayOverride({
- required int assignid,
- int? userId,
- int? groupId,
- int? allowsubmissionsfromdate,
- int? dueDate,
- int? cutoffDate,
- int? timelimit,
- int? sortorder,
- }) async {
- if (_userToken == null) throw StateError('User not logged in to Moodle');
-
- final url = Uri.parse('$apiURL$serverUrl');
-
- // Dynamically build request body, removing null values
- final Map body = {
- 'wstoken': _userToken!,
- 'wsfunction': 'local_learninglens_add_essay_override',
- 'moodlewsrestformat': 'json',
- 'assignid': assignid.toString(),
- };
-
- // Add only non-null fields
- if (userId != null) body['userid'] = userId.toString();
- if (groupId != null) body['groupid'] = groupId.toString();
- if (allowsubmissionsfromdate != null)
- body['allowsubmissionsfromdate'] = allowsubmissionsfromdate.toString();
- if (dueDate != null) body['duedate'] = dueDate.toString();
- if (cutoffDate != null) body['cutoffdate'] = cutoffDate.toString();
- if (timelimit != null) body['timelimit'] = timelimit.toString();
- if (sortorder != null) body['sortorder'] = sortorder.toString();
-
- final response = await ApiService().httpPost(url, body: body);
-
- final responseData = json.decode(response.body);
-
- if (response.statusCode == 200 && responseData is Map) {
- return 'Override: ${responseData['override_id']}';
- } else {
- throw Exception("Failed to create quiz override: ${response.body}");
- }
- }
-
-/**
- * Fetches all lesson plans associated with a given course ID.
- */
- Future> getLessonPlans(int? courseId) async {
- if (_userToken == null) throw StateError('User not logged in to Moodle');
-
- final url = Uri.parse('$apiURL$serverUrl');
-
- final response = await ApiService().httpPost(
- url,
- body: {
- 'wstoken': _userToken!,
- 'wsfunction': 'local_learninglens_get_lesson_plans_by_course',
- 'moodlewsrestformat': 'json',
- 'courseid': courseId.toString(),
- },
- );
-
- // print("Lesson Plans Response: ${response.body}");
-
- if (response.statusCode == 200) {
- final List jsonList = json.decode(response.body);
- return jsonList
- .map((json) => LessonPlan.empty().fromMoodleJson(json))
- .toList();
- } else {
- throw Exception("Failed to fetch lesson plans: ${response.body}");
- }
- }
-
-// Method to create the lesson in Moodle
- Future createLesson({
- required int courseId,
- required String lessonPlanName,
- required String content,
- int introformat = 1,
- int showdescription = 1,
- int available = 0,
- int deadline = 0,
- int timelimit = 0,
- int retake = 1,
- int maxAttempts = 3,
- int usepassword = 0,
- String password = '',
- int completion = 1,
- }) async {
- try {
- print("Creating lesson with courseId: $courseId, name: $lessonPlanName");
-
- final response = await ApiService().httpPost(
- Uri.parse(apiURL + serverUrl),
- body: {
- "wstoken": _userToken,
- "wsfunction": "local_learninglens_create_lesson",
- "moodlewsrestformat": "json",
- // REQUIRED fields
- "courseid": courseId.toString(),
- "name": lessonPlanName,
- "intro": content,
- // OPTIONAL fields
- "introformat": introformat.toString(),
- "showdescription": showdescription.toString(),
- "available": available.toString(),
- "deadline": deadline.toString(),
- "timelimit": timelimit.toString(),
- "retake": retake.toString(),
- "maxattempts": maxAttempts.toString(),
- "usepassword": usepassword.toString(),
- "password": password,
- "completion": completion.toString(),
- },
- );
-
- final jsonResponse = json.decode(response.body) as Map;
- print("createLesson Response: $jsonResponse");
-
- if (jsonResponse.containsKey("exception")) {
- print("Error creating lesson: ${jsonResponse['message']}");
- return false;
- }
-
- print(
- "Lesson created successfully! Lesson ID: ${jsonResponse['lessonId']}");
- print("Linked to Course Module ID: ${jsonResponse['courseModuleId']}");
-
- return true;
- } catch (e) {
- print("Error occurred while creating lesson: $e");
- return false;
- }
- }
-
- /**
- * Deletes a lesson plan from Moodle by its lesson ID.
- */
- Future deleteLessonPlan(int lessonId) async {
- if (_userToken == null) throw StateError('User not logged in to Moodle');
-
- final url = Uri.parse('$apiURL$serverUrl');
-
- final response = await ApiService().httpPost(
- url,
- body: {
- 'wstoken': _userToken!,
- 'wsfunction': 'local_learninglens_delete_lesson_plan',
- 'moodlewsrestformat': 'json',
- 'lessonid': lessonId.toString(),
- },
- );
-
- print("Delete Lesson Plan Response: ${response.body}");
-
- if (response.statusCode == 200) {
- final Map jsonResponse = json.decode(response.body);
-
- if (jsonResponse.containsKey("status") &&
- jsonResponse["status"] == "success") {
- print("Lesson plan deleted successfully.");
- return true;
- } else {
- print("Error deleting lesson plan: ${jsonResponse['message']}");
- return false;
- }
- } else {
- throw Exception("Failed to delete lesson plan: ${response.body}");
- }
- }
-
- /**
- * Updates a lesson plan in Moodle by its lesson ID.
- */
- Future updateLessonPlan({
- required int lessonId,
- String? name,
- String? intro,
- int? available,
- int? deadline,
- }) async {
- if (_userToken == null) throw StateError('User not logged in to Moodle');
-
- final url = Uri.parse('$apiURL$serverUrl');
-
- // Construct request body dynamically
- final Map body = {
- 'wstoken': _userToken!,
- 'wsfunction': 'local_learninglens_update_lesson_plan',
- 'moodlewsrestformat': 'json',
- 'lessonid': lessonId.toString(),
- };
-
- if (name != null) body['name'] = name;
- if (intro != null) body['intro'] = intro;
- if (available != null) body['available'] = available.toString();
- if (deadline != null) body['deadline'] = deadline.toString();
-
- final response = await ApiService().httpPost(url, body: body);
-
- print("Update Lesson Plan Response: ${response.body}");
-
- if (response.statusCode == 200) {
- final Map jsonResponse = json.decode(response.body);
-
- if (jsonResponse.containsKey("status") &&
- jsonResponse["status"] == "success") {
- print("Lesson plan updated successfully.");
- return true;
- } else {
- print("Error updating lesson plan: ${jsonResponse['message']}");
- return false;
- }
- } else {
- throw Exception("Failed to update lesson plan: ${response.body}");
- }
- }
-
- Future> getQuizGradesForParticipants(
- String courseId, int quizId) async {
- if (_userToken == null) throw StateError('User not logged in to Moodle');
-
- // Get all course participants (filtered to only students)
- List students = (await getCourseParticipants(courseId))
- .where((participant) => participant.roles.contains('student'))
- .toList();
-
- for (var student in students) {
- final response = await ApiService().httpPost(
- Uri.parse(apiURL + serverUrl),
- body: {
- 'wstoken': _userToken!,
- 'wsfunction': 'mod_quiz_get_user_best_grade',
- 'moodlewsrestformat': 'json',
- 'quizid': quizId.toString(),
- 'userid': student.id.toString(),
- },
- );
-
- if (response.statusCode != 200) {
- print(
- 'Failed to get grade for user ${student.id}. Status: ${response.statusCode}');
- continue;
- }
-
- final jsonResponse = jsonDecode(response.body);
- if (jsonResponse is Map &&
- jsonResponse.containsKey('grade')) {
- student.avgGrade = double.tryParse(jsonResponse['grade'].toString());
- } else {
- student.avgGrade = null; // No grade found
- }
- }
- return students;
- }
-
- Future> getEssayGradesForParticipants(
- String courseId, int assignmentId) async {
- if (_userToken == null) throw StateError('User not logged in to Moodle');
-
- // Get all course participants (filtered to only students)
- List students = (await getCourseParticipants(courseId))
- .where((participant) => participant.roles.contains('student'))
- .toList();
-
- for (var student in students) {
- final response = await ApiService().httpPost(
- Uri.parse(apiURL + serverUrl),
- body: {
- 'wstoken': _userToken!,
- 'wsfunction': 'mod_assign_get_grades',
- 'moodlewsrestformat': 'json',
- 'assignmentids[0]': assignmentId.toString(),
- },
- );
-
- if (response.statusCode != 200) {
- print(
- 'Failed to get grade for user ${student.id}. Status: ${response.statusCode}');
- continue;
- }
-
- final jsonResponse = jsonDecode(response.body);
- if (jsonResponse is Map &&
- jsonResponse.containsKey('assignments')) {
- final assignments = jsonResponse['assignments'] as List;
- for (var assignment in assignments) {
- final grades = assignment['grades'] as List;
- for (var grade in grades) {
- if (grade['userid'] == student.id) {
- student.avgGrade = double.tryParse(grade['grade'].toString());
- break;
- }
- }
- }
- } else {
- student.avgGrade = null; // No grade found
- }
- }
- return students;
- }
-
-
- /// Fetches extended question stats (correct/incorrect/partial attempts)
- /// from the local_learninglens_get_question_stats_from_quiz service.
- Future> getQuestionStatsFromQuiz(int quizId) async {
- if (_userToken == null) {
- throw StateError('User not logged in to Moodle');
- }
-
- final url = Uri.parse('$apiURL$serverUrl');
-
- final response = await ApiService().httpPost(
- url,
- body: {
- 'wstoken': _userToken!,
- 'wsfunction': 'local_learninglens_get_question_stats_from_quiz',
- 'moodlewsrestformat': 'json',
- 'quizid': quizId.toString(),
- },
- );
-
- if (response.statusCode != 200) {
- throw Exception('Failed to fetch question stats: ${response.body}');
- }
- print(quizId);
- print(response.body);
- final decoded = json.decode(response.body);
- if (decoded is! List) {
- throw Exception(
- 'Expected a list of question stats but got: ${response.body}',
- );
- }
-
- // Convert each item into a QuestionStatsType instance
- return decoded.map((item) {
- return QuestionStatsType.fromJson(item);
- }).toList();
- }
-}
diff --git a/team_a/teamA/lib/Api/lms/template/api_singleton.dart b/team_a/teamA/lib/Api/lms/template/api_singleton.dart
deleted file mode 100644
index 5c60ebd9..00000000
--- a/team_a/teamA/lib/Api/lms/template/api_singleton.dart
+++ /dev/null
@@ -1,236 +0,0 @@
-import 'package:learninglens_app/beans/course.dart';
-import 'package:learninglens_app/beans/lesson_plan.dart';
-import 'package:learninglens_app/beans/quiz.dart';
-import 'package:learninglens_app/beans/quiz_type.dart';
-import 'package:learninglens_app/beans/assignment.dart';
-import 'package:learninglens_app/beans/participant.dart';
-import 'package:learninglens_app/beans/submission_status.dart';
-import 'package:learninglens_app/beans/grade.dart';
-import 'package:learninglens_app/beans/submission.dart';
-import 'package:learninglens_app/beans/submission_with_grade.dart';
-import 'package:learninglens_app/beans/moodle_rubric.dart';
-import 'package:learninglens_app/Api/lms/lms_interface.dart';
-
-
-class ApiSingleton implements LmsInterface {
- static final ApiSingleton _instance = ApiSingleton._internal();
-
- ApiSingleton._internal();
-
- factory ApiSingleton() {
- return _instance;
- }
-
- String? _userToken = '';
-
- @override
- String serverUrl = '';
-
- @override
- String apiURL = '';
-
- @override
- String? userName;
-
- @override
- String? firstName;
-
- @override
- String? lastName;
-
- @override
- String? siteName;
-
- @override
- String? fullName;
-
- @override
- String? profileImage;
-
- @override
- List? courses;
-
-
- // Authentication/Login methods
- @override
- Future login(String username, String password, String baseURL) {
- // TODO: implement login
- throw UnimplementedError();
- }
-
- @override
- bool isLoggedIn() {
- return _userToken != null;
- }
-
- @override
- Future isUserTeacher(List moodleCourses) {
- // TODO: implement isUserTeacher
- throw UnimplementedError();
- }
-
- @override
- void logout() {
- print('Logging out of LMS...');
- resetLMSUserInfo();
- }
-
- @override
- void resetLMSUserInfo() {
- _userToken = null;
- apiURL = '';
- userName = null;
- firstName = null;
- lastName = null;
- siteName = null;
- fullName = null;
- profileImage = null;
- courses = [];
- }
-
- // Course methods
- @override
- Future> getCourses() {
- // TODO: implement getCourses
- throw UnimplementedError();
- }
-
- @override
- Future> getUserCourses() {
- // TODO: implement getUserCourses
- throw UnimplementedError();
- }
-
- @override
- Future> getCourseParticipants(String courseId) {
- // TODO: implement getCourseParticipants
- throw UnimplementedError();
- }
-
-
- // Quiz methods
- @override
- Future importQuiz(String courseid, String quizXml) {
- // TODO: implement importQuiz
- throw UnimplementedError();
- }
-
- @override
- Future> getQuizzes(int? courseID, {int? topicId}) {
- // TODO: implement getQuizzes
- throw UnimplementedError();
- }
-
- @override
- Future createQuiz(String courseid, String quizname, String quizintro, String sectionid, String timeopen, String timeclose) {
- // TODO: implement createQuiz
- throw UnimplementedError();
- }
-
- @override
- Future importQuizQuestions(String courseid, String quizXml) {
- // TODO: implement importQuizQuestions
- throw UnimplementedError();
- }
-
- @override
- Future addRandomQuestions(String categoryid, String quizid, String numquestions) {
- // TODO: implement addRandomQuestions
- throw UnimplementedError();
- }
-
- @override
- Future> getQuestionsFromQuiz(int quizId) async {
- // TODO: implement google api code
- throw UnimplementedError();
- }
-
-
- // Assignment methods
- @override
- Future> getEssays(int? courseID, {int? topicId}) {
- // TODO: implement getEssays
- throw UnimplementedError();
- }
-
- @override
- Future?> createAssignment(String courseid, String sectionid, String assignmentName, String startdate, String enddate, String rubricJson, String description) {
- // TODO: implement createAssignment
- throw UnimplementedError();
- }
-
- @override
- Future getContextId(int assignmentId, String courseId) {
- // TODO: implement getContextId
- throw UnimplementedError();
- }
-
- // Submission and grading methods
- @override
- Future> getAssignmentSubmissions(int assignmentId) {
- // TODO: implement getAssignmentSubmissions
- throw UnimplementedError();
- }
-
- @override
- Future> getSubmissionsWithGrades(int assignmentId) {
- // TODO: implement getSubmissionsWithGrades
- throw UnimplementedError();
- }
-
- @override
- Future getSubmissionStatus(int assignmentId, int userId) {
- // TODO: implement getSubmissionStatus
- throw UnimplementedError();
- }
-
- @override
- Future> getAssignmentGrades(int assignmentId) {
- // TODO: implement getAssignmentGrades
- throw UnimplementedError();
- }
-
- @override
- Future setRubricGrades(int assignmentId, int userId, String jsonGrades) {
- // TODO: implement setRubricGrades
- throw UnimplementedError();
- }
-
- @override
- Future getRubricGrades(int assignmentId, int userid) {
- // TODO: implement getRubricGrades
- throw UnimplementedError();
- }
-
- @override
- Grade? findGradeForUser(List grades, int userId) {
- // TODO: implement findGradeForUser
- throw UnimplementedError();
- }
-
- // Rubric methods
- @override
- Future getRubric(String assignmentid) {
- // TODO: implement getRubric
- throw UnimplementedError();
- }
-
- //Get Lesson Plan
- FuturegetLessonPlan(String courseId){
- //\TODO: implement getLessonPlan
- throw UnimplementedError();
- }
-
- @override
- Future> getQuestionsFromQuizGoogle(String courseId, String courseWorkId) async {
- // TODO: implement google api code
- throw UnimplementedError();
- }
-
- @override
- Future> getQuizGradesForParticipants(String courseId, int quizId) {
- // TODO: implement getQuizGradesForParticipants
- throw UnimplementedError();
- }
-
-}
\ No newline at end of file
diff --git a/team_a/teamA/lib/Controller/assessment_generator.dart b/team_a/teamA/lib/Controller/assessment_generator.dart
deleted file mode 100644
index a8a97c46..00000000
--- a/team_a/teamA/lib/Controller/assessment_generator.dart
+++ /dev/null
@@ -1,26 +0,0 @@
-import 'dart:convert';
-import 'package:learninglens_app/beans/quiz.dart';
-import 'package:learninglens_app/services/api_service.dart';
-
-class AssessmentGenerator
-{
- final String serverUrl;
-
- AssessmentGenerator({required this.serverUrl});
-
- Future generateAssessment(String queryPrompt) async {
- final response = await ApiService().httpPost(
- Uri.parse('$serverUrl/generateAssessment'),
- headers: {'Content-Type': 'application/x-www-form-urlencoded'},
- body: {'QueryPrompt': queryPrompt},
- );
-
- if (response.statusCode == 200) {
- final responseBody = json.decode(response.body);
- final aiResponse = responseBody['assessment'];
- return Quiz.fromXmlString(aiResponse);
- } else {
- throw Exception('Failed to generate assessment');
- }
- }
-}
diff --git a/team_a/teamA/lib/Controller/custom_appbar.dart b/team_a/teamA/lib/Controller/custom_appbar.dart
deleted file mode 100644
index b212a208..00000000
--- a/team_a/teamA/lib/Controller/custom_appbar.dart
+++ /dev/null
@@ -1,195 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:learninglens_app/Api/experimental/assistant/textbased_function_caller_view.dart';
-import 'package:learninglens_app/Api/lms/enum/lms_enum.dart';
-import 'package:learninglens_app/Views/dashboard.dart';
-import 'package:learninglens_app/Views/user_settings.dart';
-import 'package:learninglens_app/Views/chat_screen.dart';
-import 'package:learninglens_app/services/local_storage_service.dart';
-
-class ClassroomSelection {
- static LmsType selectedClassroom = LocalStorageService.getSelectedClassroom() == LmsType.MOODLE
- ? LmsType.MOODLE
- : LmsType.GOOGLE;
-}
-
-class CustomAppBar extends StatefulWidget implements PreferredSizeWidget {
- final String title;
- final String userprofileurl;
- final VoidCallback? onRefresh; // Optional refresh callback
-
- CustomAppBar({required this.title, required this.userprofileurl, this.onRefresh});
-
- @override
- _CustomAppBarState createState() => _CustomAppBarState();
-
- @override
- Size get preferredSize => Size.fromHeight(kToolbarHeight);
-}
-
-class _CustomAppBarState extends State {
- @override
- Widget build(BuildContext context) {
- return AppBar(
- backgroundColor: Theme.of(context).colorScheme.primaryContainer,
- title: Text(
- widget.title,
- textAlign: TextAlign.center,
- ),
- centerTitle: true,
- leadingWidth: 120,
- leading: Row(
- mainAxisSize: MainAxisSize.max,
- mainAxisAlignment: MainAxisAlignment.spaceEvenly,
- children: [
- Flexible(
- child: IconButton(
- icon: Icon(Icons.arrow_back),
- onPressed: isDashboard(context)
- ? null
- : () {
- Navigator.pop(context);
- },
- ),
- ),
- Flexible(
- child: IconButton(
- icon: Icon(Icons.home),
- onPressed: isDashboard(context)
- ? null
- : () {
- navigateToSelectedDashboard(context);
- },
- ),
- ),
- Flexible(
- child: IconButton(
- icon: Icon(Icons.chat_rounded),
- onPressed: () {
- Navigator.pushReplacement(
- context,
- MaterialPageRoute(
- builder: (context) => ChatScreen()
- )
- );
- },
- ),
- ),
- ],
- ),
- actions: [
-
- Flexible(
- child: IconButton(
- icon: Icon(Icons.science), // Science Icon
- onPressed: () {
- Navigator.push(
- context,
- MaterialPageRoute(builder: (context) => TextBasedFunctionCallerView()),
- );
- },
- ),
- ),
- // Refresh button: Instead of relying on an external callback,
- // try to obtain the current route's name and then replace the route,
- // effectively refreshing the current view.
- IconButton(
- icon: Icon(Icons.refresh),
- onPressed: () {
- final currentRouteName = ModalRoute.of(context)?.settings.name;
- if (currentRouteName != null) {
- Navigator.pushReplacementNamed(context, currentRouteName);
- } else {
- // Fallback: if no route name is available, call the provided onRefresh callback if any.
- widget.onRefresh?.call();
- }
- },
- ),
- DropdownButtonHideUnderline(
- child: DropdownButton(
- value: ClassroomSelection.selectedClassroom,
- onChanged: isDashboard(context)
- ? (LmsType? newValue) {
- if (newValue != null) {
- setState(() {
- ClassroomSelection.selectedClassroom = newValue;
- });
- navigateToSelectedDashboard(context);
- }
- }
- : null,
- items: LmsType.values.map>((LmsType value) {
- return DropdownMenuItem(
- value: value,
- child: Text(
- value == LmsType.GOOGLE ? 'Google Classroom' : 'Moodle Classroom',
- ),
- );
- }).toList(),
- ),
- ),
- IconButton(
- icon: Icon(Icons.settings),
- onPressed: !isDashboard(context)
- ? null
- : () {
- Navigator.push(
- context,
- MaterialPageRoute(builder: (context) => UserSettings()),
- );
- },
- ),
- Padding(
- padding: EdgeInsets.only(right: 10.0),
- child: InkWell(
- child: Material(
- color: Colors.transparent,
- child: Container(
- width: 50,
- height: 50,
- decoration: BoxDecoration(
- shape: BoxShape.circle,
- ),
- child: ClipOval(
- child: Image.network(
- widget.userprofileurl,
- height: 50,
- width: 50,
- fit: BoxFit.cover,
- errorBuilder: (BuildContext context, Object error, StackTrace? stackTrace) {
- // print('Image load error: $error');
- // Just display the account_circle icon be default
- return Icon(Icons.account_circle, size: 50);
- },
- ),
- ),
- ),
- ),
- ),
- ),
- ],
- );
- }
-
- bool isDashboard(BuildContext context) {
- return widget.title == 'Learning Lens';
- }
-
- void navigateToSelectedDashboard(BuildContext context) {
- if (ClassroomSelection.selectedClassroom == LmsType.GOOGLE) {
- LocalStorageService.saveSelectedClassroom(LmsType.GOOGLE);
- } else {
- LocalStorageService.saveSelectedClassroom(LmsType.MOODLE);
- }
-
- Navigator.pushReplacement(
- context,
- MaterialPageRoute(
- builder: (context) => TeacherDashboard(),
- // builder: (context) => ClassroomSelection.selectedClassroom == LmsType.GOOGLE
- // // ? GoogleTeacherDashboard()
- // ? TeacherDashboard()
- // : TeacherDashboard(),
- ),
- );
- }
-}
diff --git a/team_a/teamA/lib/Controller/essay_generator.dart b/team_a/teamA/lib/Controller/essay_generator.dart
deleted file mode 100644
index 18b5370b..00000000
--- a/team_a/teamA/lib/Controller/essay_generator.dart
+++ /dev/null
@@ -1,223 +0,0 @@
-import 'dart:convert';
-import 'package:flutter/services.dart';
-import 'package:flutter/material.dart';
-import 'package:learninglens_app/beans/criterion.dart';
-
-class Essay
-{
- //Holds the assignment prompt
- String? prompt;
- //Holds the course level information
- String? courseLevel;
- //Holds the list of crtiera for a given assignment
- List criteria;
- //Constructor for a standard essay
- Essay(this.prompt, this.courseLevel, this.criteria);
- //Generates the table data for the rubric based on the criteria for the essay object. Does not create header row.
- List> genTableData()
- {
- List critList = criteria;
- List> tableData = [];
- for (Criterion c in critList)
- {
- int iterator = 1;
- Map tempMap = {};
- String currName = c.name;
- num currWeight = c.points;
- Map desc = c.descriptions;
- for (MapEntry d in desc.entries) {
- if (iterator == 1) {
- tempMap[''] = '$currName\n\nPoints: $currWeight';
- iterator++;
- }
- tempMap[d.key] = d.value;
- iterator++;
- }
- tableData.add(tempMap);
- }
- return tableData;
- }
- //Creates the header row for the table data
- List genHeaderData()
- {
- List headerData = criteria[0].descriptions.keys.toList();
- headerData.insert(0, '');
- return headerData;
- }
-}
-
-
-// Fetch Criteria data from JSON file and add it to the program
-Future> fetchJsonCriteria() async
-{
- final String jsonString = await rootBundle.loadString('assets/Criteria.json');
- final Map jsonData = json.decode(jsonString);
- List critList = jsonData['Criteria']; // Access the 'users' key
- return critList.map((critJson) => Criterion.fromJson(critJson)).toList();
-}
-
-// Take the default descriptions from the JSON and build full list of critieria based on scale
-Future> generateCriteria(
- int scale, List selectedCriteria) async {
- List allCriteria = await fetchJsonCriteria() as List;
- List