|
@@ -19,19 +19,11 @@ class FileUploadApi extends BaseApi {
|
|
|
|
|
|
|
|
SupabaseClient getSupabaseClient(shelf.Request request) {
|
|
SupabaseClient getSupabaseClient(shelf.Request request) {
|
|
|
final supabaseUrl = request.headers['supabase-url'];
|
|
final supabaseUrl = request.headers['supabase-url'];
|
|
|
- if (supabaseUrl == null) {
|
|
|
|
|
- throw Exception('Supabase URL not provided in headers');
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
final authHeader = request.headers['authorization'];
|
|
final authHeader = request.headers['authorization'];
|
|
|
- if (authHeader == null || !authHeader.startsWith('Bearer ')) {
|
|
|
|
|
- throw Exception('Invalid or missing Authorization Bearer token');
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- final token = authHeader.substring(7); // Remove 'Bearer ' prefix
|
|
|
|
|
|
|
+ final token = authHeader!.substring(7); // Remove 'Bearer ' prefix
|
|
|
|
|
|
|
|
return SupabaseClient(
|
|
return SupabaseClient(
|
|
|
- supabaseUrl,
|
|
|
|
|
|
|
+ supabaseUrl!,
|
|
|
token,
|
|
token,
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
@@ -110,6 +102,18 @@ class FileUploadApi extends BaseApi {
|
|
|
|
|
|
|
|
@override
|
|
@override
|
|
|
response() async {
|
|
response() async {
|
|
|
|
|
+ final supabaseUrl = request.headers['supabase-url'];
|
|
|
|
|
+ final authHeader = request.headers['authorization'];
|
|
|
|
|
+
|
|
|
|
|
+ if (authHeader == null || !authHeader.startsWith('Bearer ')) {
|
|
|
|
|
+ return shelf.Response.badRequest(
|
|
|
|
|
+ body: 'Invalid or missing Authorization Bearer token');
|
|
|
|
|
+ }
|
|
|
|
|
+ if (supabaseUrl == null) {
|
|
|
|
|
+ return shelf.Response.badRequest(
|
|
|
|
|
+ body: 'Supabase URL not provided in headers');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
workingFolder = DateTime.now().millisecondsSinceEpoch.toString();
|
|
workingFolder = DateTime.now().millisecondsSinceEpoch.toString();
|
|
|
|
|
|
|
|
final supabaseClient = getSupabaseClient(request);
|
|
final supabaseClient = getSupabaseClient(request);
|
|
@@ -175,12 +179,12 @@ class FileUploadApi extends BaseApi {
|
|
|
|
|
|
|
|
for (var file in files) {
|
|
for (var file in files) {
|
|
|
final fileProcess = FileProcess(file, supabaseClient);
|
|
final fileProcess = FileProcess(file, supabaseClient);
|
|
|
- await fileProcess.go(donttouchdb: true);
|
|
|
|
|
|
|
+ await fileProcess.go(donttouchdb: false);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return shelf.Response.ok('File processed and uploaded successfully');
|
|
return shelf.Response.ok('File processed and uploaded successfully');
|
|
|
- } catch (e, stackTrace) {
|
|
|
|
|
|
|
+ } catch (e) {
|
|
|
//print('Error: $e\n$stackTrace');
|
|
//print('Error: $e\n$stackTrace');
|
|
|
return shelf.Response.internalServerError(
|
|
return shelf.Response.internalServerError(
|
|
|
body: 'Error processing upload: $e');
|
|
body: 'Error processing upload: $e');
|
|
@@ -309,53 +313,97 @@ class FileProcess {
|
|
|
"ExportPGRGPNmois.txt": "date",
|
|
"ExportPGRGPNmois.txt": "date",
|
|
|
"exportlicence.txt": "tlc",
|
|
"exportlicence.txt": "tlc",
|
|
|
};
|
|
};
|
|
|
- final Map<String, List<String>> idToRemove = {
|
|
|
|
|
- "secondprgtype.txt": ["day_of_origin"],
|
|
|
|
|
- "exportPGRGPN.txt": ["date", "tlc"],
|
|
|
|
|
- "ExportPGRGPNmois.txt": ["date", "tlc"],
|
|
|
|
|
- "exportlicence.txt": ["tlc"],
|
|
|
|
|
|
|
+ final Map<String, String> logTables = {
|
|
|
|
|
+ "secondprgtype.txt": "aclegs_csv_log",
|
|
|
|
|
+ "ExportPGRGPNmois.txt": "pnlegs_csv_log",
|
|
|
|
|
+ "exportPGRGPN.txt": "pnlegs_csv_log",
|
|
|
|
|
+ "exportlicence.txt": "licences_csv_log",
|
|
|
};
|
|
};
|
|
|
|
|
+ //all tables trackers: key,add,remove
|
|
|
final Map<String, List<Map<String, dynamic>>> trackers = {
|
|
final Map<String, List<Map<String, dynamic>>> trackers = {
|
|
|
-/* "secondprgtype.txt": {
|
|
|
|
|
- "table": "aclegs_log",
|
|
|
|
|
- "headers": [
|
|
|
|
|
- "day_of_origin",
|
|
|
|
|
- "dep_sched_dt",
|
|
|
|
|
- "fn_carrier",
|
|
|
|
|
- "fn_number",
|
|
|
|
|
- "dep_ap_sched",
|
|
|
|
|
- "arr_ap_sched",
|
|
|
|
|
- // "dep_ap_actual",
|
|
|
|
|
- // "arr_ap_actual"
|
|
|
|
|
- ]
|
|
|
|
|
- },
|
|
|
|
|
- */
|
|
|
|
|
|
|
+ "secondprgtype.txt": [
|
|
|
|
|
+ {
|
|
|
|
|
+ "table": "aclegs_log_reg",
|
|
|
|
|
+ "groupby": [
|
|
|
|
|
+ "day_of_origin",
|
|
|
|
|
+ "dep_sched_dt",
|
|
|
|
|
+ "fn_carrier",
|
|
|
|
|
+ "fn_number",
|
|
|
|
|
+ "dep_ap_sched",
|
|
|
|
|
+ "arr_ap_sched",
|
|
|
|
|
+ // "dep_ap_actual",
|
|
|
|
|
+ // "arr_ap_actual"
|
|
|
|
|
+ ],
|
|
|
|
|
+ "track": [
|
|
|
|
|
+ "ac_registration",
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ "table": "aclegs_log_leg",
|
|
|
|
|
+ "groupby": [
|
|
|
|
|
+ "day_of_origin",
|
|
|
|
|
+ "dep_sched_dt",
|
|
|
|
|
+ "fn_carrier",
|
|
|
|
|
+ "fn_number",
|
|
|
|
|
+ "dep_ap_sched",
|
|
|
|
|
+ "arr_ap_sched",
|
|
|
|
|
+ ],
|
|
|
|
|
+ "track": [
|
|
|
|
|
+ "dep_ap_actual",
|
|
|
|
|
+ "arr_ap_actual",
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
"exportPGRGPN.txt": [
|
|
"exportPGRGPN.txt": [
|
|
|
{
|
|
{
|
|
|
"table": "pnlegs_log_roster",
|
|
"table": "pnlegs_log_roster",
|
|
|
"groupby": ["date", "tlc"],
|
|
"groupby": ["date", "tlc"],
|
|
|
"track": ["dep", "des", "al", "fnum", "label"]
|
|
"track": ["dep", "des", "al", "fnum", "label"]
|
|
|
},
|
|
},
|
|
|
- // {
|
|
|
|
|
- // "table": "pnlegs_log_duty",
|
|
|
|
|
- // "groupby": ["date", "dep", "des", "al", "fnum", "label"],
|
|
|
|
|
- // "track": ["tlc"]
|
|
|
|
|
- // },
|
|
|
|
|
- // {
|
|
|
|
|
- // "table": "pnlegs_log_sched",
|
|
|
|
|
- // "groupby": ["date", "dep", "des", "al", "fnum", "label"],
|
|
|
|
|
- // "changes": ["hdep", "hdes"]
|
|
|
|
|
- // },
|
|
|
|
|
|
|
+ {
|
|
|
|
|
+ "table": "pnlegs_log_duty",
|
|
|
|
|
+ "groupby": ["date", "dep", "des", "al", "fnum", "label"],
|
|
|
|
|
+ "track": ["tlc"]
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ "table": "pnlegs_log_sched",
|
|
|
|
|
+ "groupby": ["date", "dep", "des", "al", "fnum", "label"],
|
|
|
|
|
+ "track": ["hdep", "hdes"]
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ "ExportPGRGPNmois.txt": [
|
|
|
|
|
+ {
|
|
|
|
|
+ "table": "pnlegs_log_roster",
|
|
|
|
|
+ "groupby": ["date", "tlc"],
|
|
|
|
|
+ "track": ["dep", "des", "al", "fnum", "label"]
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ "table": "pnlegs_log_duty",
|
|
|
|
|
+ "groupby": ["date", "dep", "des", "al", "fnum", "label"],
|
|
|
|
|
+ "track": ["tlc"]
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ "table": "pnlegs_log_sched",
|
|
|
|
|
+ "groupby": ["date", "dep", "des", "al", "fnum", "label"],
|
|
|
|
|
+ "track": ["hdep", "hdes"]
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ "exportlicence.txt": [
|
|
|
|
|
+ {
|
|
|
|
|
+ "table": "licences_log_qualif",
|
|
|
|
|
+ "groupby": [
|
|
|
|
|
+ "tlc",
|
|
|
|
|
+ "fname",
|
|
|
|
|
+ "mname",
|
|
|
|
|
+ "lname",
|
|
|
|
|
+ ],
|
|
|
|
|
+ "track": [
|
|
|
|
|
+ "ac",
|
|
|
|
|
+ "college",
|
|
|
|
|
+ "base",
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
],
|
|
],
|
|
|
-/* "ExportPGRGPNmois.txt": {
|
|
|
|
|
- "table": "pnlegs_log",
|
|
|
|
|
- "headers": ["tlc", "date", "dep", "des", "al", "fnum", "label"]
|
|
|
|
|
- },
|
|
|
|
|
- "exportlicence.txt": {
|
|
|
|
|
- "table": "qualifs_log",
|
|
|
|
|
- "headers": ["tlc", "college", "ac", "base"]
|
|
|
|
|
- },
|
|
|
|
|
- */
|
|
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
Future<List<Map<String, dynamic>>> parseCsv() async {
|
|
Future<List<Map<String, dynamic>>> parseCsv() async {
|
|
@@ -376,7 +424,7 @@ class FileProcess {
|
|
|
// Split the line into individual values
|
|
// Split the line into individual values
|
|
|
final values = lines[i].split(',');
|
|
final values = lines[i].split(',');
|
|
|
if (values.length != headers.length) {
|
|
if (values.length != headers.length) {
|
|
|
- // print('Skipping line $i: Incorrect number of values: line: $i');
|
|
|
|
|
|
|
+ //print('Skipping line $i: Incorrect number of values: line: $i');
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -421,10 +469,14 @@ class FileProcess {
|
|
|
oldComparable.addAll(res.map((e) => e..remove("id")));
|
|
oldComparable.addAll(res.map((e) => e..remove("id")));
|
|
|
|
|
|
|
|
final comparisonResult = compareLists(oldComparable, mapsToInsert);
|
|
final comparisonResult = compareLists(oldComparable, mapsToInsert);
|
|
|
- final indexToRemove = comparisonResult.removeIndices;
|
|
|
|
|
- final indexToMaintain = comparisonResult.maintainIndices;
|
|
|
|
|
|
|
+ List<int> indexToRemove = comparisonResult.removeIndices;
|
|
|
|
|
+ List<int> indexToMaintain = comparisonResult.maintainIndices;
|
|
|
final dataToInsert = comparisonResult.insertData;
|
|
final dataToInsert = comparisonResult.insertData;
|
|
|
|
|
|
|
|
|
|
+ //special export prgpn
|
|
|
|
|
+ if (filename == "exportPGRGPN.txt" ||
|
|
|
|
|
+ filename == "ExportPGRGPNmois.txt") {}
|
|
|
|
|
+
|
|
|
try {
|
|
try {
|
|
|
if (!donttouchdb)
|
|
if (!donttouchdb)
|
|
|
for (var e in chunkList(
|
|
for (var e in chunkList(
|
|
@@ -446,39 +498,84 @@ class FileProcess {
|
|
|
print(
|
|
print(
|
|
|
" Scope:$scopeInNew insert:${dataToInsert.length} remove:${indexToRemove.length} maintain:${indexToMaintain.length}");
|
|
" Scope:$scopeInNew insert:${dataToInsert.length} remove:${indexToRemove.length} maintain:${indexToMaintain.length}");
|
|
|
|
|
|
|
|
|
|
+ //logging changes into tables
|
|
|
|
|
+
|
|
|
|
|
+ final logTable = logTables[filename]!;
|
|
|
|
|
+ final logData = dataToInsert
|
|
|
|
|
+ .map((e) => {"scope": scopeInNew, "data": e, "action": "insert"})
|
|
|
|
|
+ .toList();
|
|
|
|
|
+ for (var e in chunkList(
|
|
|
|
|
+ indexToRemove.map((f) => oldComparable[f]).toList(), 100)) {
|
|
|
|
|
+ // e.forEach((k) => print("log: -: $k"));
|
|
|
|
|
+ if (!donttouchdb)
|
|
|
|
|
+ await supabase
|
|
|
|
|
+ .from(logTable) // Replace with your actual table name
|
|
|
|
|
+ .insert(e
|
|
|
|
|
+ .map((e) =>
|
|
|
|
|
+ {"scope": scopeInNew, "data": e, "action": "delete"})
|
|
|
|
|
+ .toList());
|
|
|
|
|
+ }
|
|
|
|
|
+ for (var e in chunkList(logData, 100)) {
|
|
|
|
|
+ // e.forEach((k) => print("log: +: $k"));
|
|
|
|
|
+ if (!donttouchdb) await supabase.from(logTable).insert(e);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+//logging tracking data
|
|
|
for (var tracker in trackers[filename] ?? []) {
|
|
for (var tracker in trackers[filename] ?? []) {
|
|
|
- final table = tracker["table"];
|
|
|
|
|
- final groupby = tracker["groupby"] ?? [];
|
|
|
|
|
- final track = tracker["track"] ?? [];
|
|
|
|
|
|
|
+ final String table = tracker["table"];
|
|
|
|
|
+ final List<String> groupby = tracker["groupby"] ?? [];
|
|
|
|
|
+ final List<String> track = tracker["track"] ?? [];
|
|
|
final stateOld = oldComparable.groupBy(
|
|
final stateOld = oldComparable.groupBy(
|
|
|
(e) => groupby.map((f) => e[f]).join("|"),
|
|
(e) => groupby.map((f) => e[f]).join("|"),
|
|
|
- dataFunction: (e) => e
|
|
|
|
|
- .filterKeys(track)
|
|
|
|
|
- .values
|
|
|
|
|
- .map((j) => j == null ? "" : j)
|
|
|
|
|
- .join("_"));
|
|
|
|
|
- final stateNew = dataToInsert.groupBy(
|
|
|
|
|
|
|
+ dataFunction: (e) =>
|
|
|
|
|
+ e.filterKeys(track).values.map((j) => j ?? "").join("|"));
|
|
|
|
|
+ final stateNew = mapsToInsert.groupBy(
|
|
|
(e) => groupby.map((f) => e[f]).join("|"),
|
|
(e) => groupby.map((f) => e[f]).join("|"),
|
|
|
- dataFunction: (e) => e
|
|
|
|
|
- .filterKeys(track)
|
|
|
|
|
- .values
|
|
|
|
|
- .map((j) => j == null ? "" : j)
|
|
|
|
|
- .join("_"));
|
|
|
|
|
|
|
+ dataFunction: (e) =>
|
|
|
|
|
+ e.filterKeys(track).values.map((j) => j ?? "").join("|"));
|
|
|
|
|
+
|
|
|
List logs = [];
|
|
List logs = [];
|
|
|
for (var key
|
|
for (var key
|
|
|
in (stateOld.keys.toList()..addAll(stateNew.keys)).toSet()) {
|
|
in (stateOld.keys.toList()..addAll(stateNew.keys)).toSet()) {
|
|
|
- final (add, remove) = (stateNew[key] ?? []).diff(stateOld[key] ?? []);
|
|
|
|
|
|
|
+ final (add, remove) =
|
|
|
|
|
+ (stateNew[key] ?? []).compareWith(stateOld[key] ?? []);
|
|
|
|
|
+ //if (!key.endsWith(tracktlc)) continue;
|
|
|
|
|
+
|
|
|
|
|
+ //foreach add remove
|
|
|
if (add.isNotEmpty || remove.isNotEmpty) {
|
|
if (add.isNotEmpty || remove.isNotEmpty) {
|
|
|
- logs.add("$key:\n +$add}\n -$remove\n");
|
|
|
|
|
|
|
+ final row = {
|
|
|
|
|
+ "key": list2map(groupby, key.split("|")),
|
|
|
|
|
+ "add": add.isNotEmpty
|
|
|
|
|
+ ? add.map((e) => list2map(track, e.split("|"))).toList()
|
|
|
|
|
+ : [],
|
|
|
|
|
+ "remove": remove.isNotEmpty
|
|
|
|
|
+ ? remove.map((e) => list2map(track, e.split("|"))).toList()
|
|
|
|
|
+ : [],
|
|
|
|
|
+ };
|
|
|
|
|
+ logs.add(row);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- print(" Tracker:$table");
|
|
|
|
|
- print(" $logs");
|
|
|
|
|
|
|
+ //print(" Tracker:$table");
|
|
|
|
|
+ for (var e in chunkList(logs, 100)) {
|
|
|
|
|
+ // e.forEach((k) => print("log: +: $k"));
|
|
|
|
|
+ if (!donttouchdb) await supabase.from(table).insert(e);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ Map<String, dynamic> list2map(List<String> keys, List<dynamic> data) {
|
|
|
|
|
+ Map<String, dynamic> map = {};
|
|
|
|
|
+ for (var i = 0; i < keys.length; i++) {
|
|
|
|
|
+ final key = keys[i];
|
|
|
|
|
+ final datum = data[i];
|
|
|
|
|
+ map[key] = datum;
|
|
|
|
|
+ }
|
|
|
|
|
+ return map;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Compare two lists of maps and return the indices to maintain, remove, and insert
|
|
|
({
|
|
({
|
|
|
List<int> maintainIndices,
|
|
List<int> maintainIndices,
|
|
|
List<int> removeIndices,
|
|
List<int> removeIndices,
|
|
@@ -488,7 +585,7 @@ class FileProcess {
|
|
|
List<Map<String, dynamic>> map1, List<Map<String, dynamic>> map2) {
|
|
List<Map<String, dynamic>> map1, List<Map<String, dynamic>> map2) {
|
|
|
List<int> maintainIndices = [];
|
|
List<int> maintainIndices = [];
|
|
|
List<int> removeIndices = [];
|
|
List<int> removeIndices = [];
|
|
|
- List<Map<String, dynamic>> insertData = map2;
|
|
|
|
|
|
|
+ List<Map<String, dynamic>> insertData = List.from(map2);
|
|
|
|
|
|
|
|
// Find indices to maintain and remove in map1
|
|
// Find indices to maintain and remove in map1
|
|
|
for (int i = 0; i < map1.length; i++) {
|
|
for (int i = 0; i < map1.length; i++) {
|
|
@@ -522,21 +619,6 @@ class FileProcess {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-extension IterableDiff<T> on Iterable<T> {
|
|
|
|
|
- (Iterable<T> add, Iterable<T> remove) diff(Iterable<T> listB) {
|
|
|
|
|
- // Convert listA to a list for easier indexing
|
|
|
|
|
- final listA = this;
|
|
|
|
|
-
|
|
|
|
|
- // Items to add are those in listB but not in listA
|
|
|
|
|
- final add = listB.where((item) => !listA.contains(item));
|
|
|
|
|
-
|
|
|
|
|
- // Items to remove are those in listA but not in listB
|
|
|
|
|
- final remove = listA.where((item) => !listB.contains(item));
|
|
|
|
|
-
|
|
|
|
|
- return (add, remove);
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
extension CompareIterables<T> on Iterable<T> {
|
|
extension CompareIterables<T> on Iterable<T> {
|
|
|
/// Compares this iterable with another iterable and returns a map containing:
|
|
/// Compares this iterable with another iterable and returns a map containing:
|
|
|
/// - 'added': Items that are in the other iterable but not in this one.
|
|
/// - 'added': Items that are in the other iterable but not in this one.
|