file_upload_api.dart 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. import 'dart:io';
  2. import 'package:file_upload_processor/handlers/base_api.dart';
  3. import 'package:intl/intl.dart';
  4. import 'package:mime/mime.dart';
  5. import 'package:shelf/shelf.dart' as shelf;
  6. import 'package:path/path.dart' as path;
  7. import 'package:http_parser/http_parser.dart';
  8. import 'package:archive/archive.dart';
  9. import 'package:supabase/supabase.dart';
  10. class FileUploadApi extends BaseApi {
  11. FileUploadApi(shelf.Request request) : super(request);
  12. SupabaseClient getSupabaseClient(shelf.Request request) {
  13. final supabaseUrl = request.headers['supabase-url'];
  14. if (supabaseUrl == null) {
  15. throw Exception('Supabase URL not provided in headers');
  16. }
  17. final authHeader = request.headers['authorization'];
  18. if (authHeader == null || !authHeader.startsWith('Bearer ')) {
  19. throw Exception('Invalid or missing Authorization Bearer token');
  20. }
  21. final token = authHeader.substring(7); // Remove 'Bearer ' prefix
  22. return SupabaseClient(
  23. supabaseUrl,
  24. token,
  25. );
  26. }
  27. String getTimestampedFilename(String originalFilename) {
  28. final timestamp = DateFormat('yyyyMMdd_HHmmss').format(DateTime.now());
  29. return '${timestamp}_$originalFilename';
  30. }
  31. Future<void> uploadToSupabase(
  32. String filePath, String filename, SupabaseClient supabaseClient,
  33. {bool timestamped = false,
  34. required String bucket,
  35. bool upsert = true}) async {
  36. try {
  37. final file = File(filePath);
  38. final timestampedFilename =
  39. timestamped ? getTimestampedFilename(filename) : filename;
  40. await supabaseClient.storage.from(bucket).upload(
  41. timestampedFilename,
  42. file,
  43. fileOptions: FileOptions(
  44. cacheControl: '3600',
  45. upsert: upsert,
  46. ),
  47. );
  48. print('File uploaded to Supabase: $timestampedFilename');
  49. } catch (e) {
  50. print('Error uploading to Supabase: $e');
  51. rethrow;
  52. }
  53. }
  54. Future<void> initializeDirectories() async {
  55. final directories = [
  56. Directory('./uploaded/raw'),
  57. Directory('./uploaded/data'),
  58. ];
  59. for (var dir in directories) {
  60. if (!await dir.exists()) {
  61. await dir.create(recursive: true);
  62. }
  63. }
  64. }
  65. bool isZipFile(List<int> bytes) {
  66. if (bytes.length < 4) return false;
  67. return bytes[0] == 0x50 &&
  68. bytes[1] == 0x4B &&
  69. bytes[2] == 0x03 &&
  70. bytes[3] == 0x04;
  71. }
  72. Future<void> processZipFile(String filePath) async {
  73. final bytes = await File(filePath).readAsBytes();
  74. final archive = ZipDecoder().decodeBytes(bytes);
  75. for (final file in archive) {
  76. final filename = file.name;
  77. if (file.isFile) {
  78. final data = file.content as List<int>;
  79. final outFile = File(path.join('./uploaded/data', filename));
  80. await outFile.parent.create(recursive: true);
  81. await outFile.writeAsBytes(data);
  82. }
  83. }
  84. }
  85. @override
  86. response() async {
  87. final supabaseClient = getSupabaseClient(request);
  88. await initializeDirectories();
  89. final contentType = request.headers['content-type'];
  90. if (contentType == null ||
  91. !contentType.toLowerCase().startsWith('multipart/form-data')) {
  92. return shelf.Response.badRequest(
  93. body: 'Content-Type must be multipart/form-data');
  94. }
  95. try {
  96. final mediaType = MediaType.parse(contentType);
  97. final boundary = mediaType.parameters['boundary'];
  98. if (boundary == null) {
  99. return shelf.Response.badRequest(body: 'Boundary not found');
  100. }
  101. final transformer = MimeMultipartTransformer(boundary);
  102. final bodyBytes = await request.read().expand((e) => e).toList();
  103. final stream = Stream.fromIterable([bodyBytes]);
  104. final parts = await transformer.bind(stream).toList();
  105. for (var part in parts) {
  106. final contentDisposition = part.headers['content-disposition'];
  107. if (contentDisposition == null) continue;
  108. final filenameMatch =
  109. RegExp(r'filename="([^"]*)"').firstMatch(contentDisposition);
  110. if (filenameMatch == null) continue;
  111. final filename = filenameMatch.group(1);
  112. if (filename == null) continue;
  113. final bytes = await part.fold<List<int>>(
  114. [],
  115. (prev, element) => [...prev, ...element],
  116. );
  117. final rawFilePath = path.join('./uploaded/raw', filename);
  118. await File(rawFilePath).writeAsBytes(bytes);
  119. if (isZipFile(bytes)) {
  120. await processZipFile(rawFilePath);
  121. } else {
  122. final dataFilePath = path.join('./uploaded/data', filename);
  123. await File(rawFilePath).copy(dataFilePath);
  124. }
  125. await uploadToSupabase(rawFilePath, filename, supabaseClient,
  126. bucket: 'csvhich_archive', timestamped: true, upsert: false);
  127. }
  128. return shelf.Response.ok('File processed and uploaded successfully');
  129. } catch (e, stackTrace) {
  130. print('Error: $e\n$stackTrace');
  131. return shelf.Response.internalServerError(
  132. body: 'Error processing upload: $e');
  133. } finally {
  134. supabaseClient.dispose();
  135. await File('./uploaded/raw').delete(recursive: true);
  136. await File('./uploaded/data').delete(recursive: true);
  137. }
  138. }
  139. }