file_upload_api.dart 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. import 'dart:io';
  2. import 'package:intl/intl.dart';
  3. import 'package:mime/mime.dart';
  4. import 'package:shelf/shelf.dart' as shelf;
  5. //import 'package:shelf/shelf_io.dart' as shelf_io;
  6. import 'package:shelf_router/shelf_router.dart';
  7. import 'package:path/path.dart' as path;
  8. import 'package:http_parser/http_parser.dart';
  9. import 'package:archive/archive.dart';
  10. import 'package:supabase/supabase.dart';
  11. class FileUploadApi {
  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. Router get router {
  86. final router = Router();
  87. router.get('/', (shelf.Request request) {
  88. return shelf.Response.ok('Hello World');
  89. });
  90. router.post('/upload', (shelf.Request request) async {
  91. final supabaseClient = getSupabaseClient(request);
  92. await initializeDirectories();
  93. final contentType = request.headers['content-type'];
  94. if (contentType == null ||
  95. !contentType.toLowerCase().startsWith('multipart/form-data')) {
  96. return shelf.Response.badRequest(
  97. body: 'Content-Type must be multipart/form-data');
  98. }
  99. try {
  100. final mediaType = MediaType.parse(contentType);
  101. final boundary = mediaType.parameters['boundary'];
  102. if (boundary == null) {
  103. return shelf.Response.badRequest(body: 'Boundary not found');
  104. }
  105. final transformer = MimeMultipartTransformer(boundary);
  106. final bodyBytes = await request.read().expand((e) => e).toList();
  107. final stream = Stream.fromIterable([bodyBytes]);
  108. final parts = await transformer.bind(stream).toList();
  109. for (var part in parts) {
  110. final contentDisposition = part.headers['content-disposition'];
  111. if (contentDisposition == null) continue;
  112. final filenameMatch =
  113. RegExp(r'filename="([^"]*)"').firstMatch(contentDisposition);
  114. if (filenameMatch == null) continue;
  115. final filename = filenameMatch.group(1);
  116. if (filename == null) continue;
  117. final bytes = await part.fold<List<int>>(
  118. [],
  119. (prev, element) => [...prev, ...element],
  120. );
  121. final rawFilePath = path.join('./uploaded/raw', filename);
  122. await File(rawFilePath).writeAsBytes(bytes);
  123. if (isZipFile(bytes)) {
  124. await processZipFile(rawFilePath);
  125. } else {
  126. final dataFilePath = path.join('./uploaded/data', filename);
  127. await File(rawFilePath).copy(dataFilePath);
  128. }
  129. await uploadToSupabase(rawFilePath, filename, supabaseClient,
  130. bucket: 'csvhich_archive', timestamped: true, upsert: false);
  131. }
  132. return shelf.Response.ok('File processed and uploaded successfully');
  133. } catch (e, stackTrace) {
  134. print('Error: $e\n$stackTrace');
  135. return shelf.Response.internalServerError(
  136. body: 'Error processing upload: $e');
  137. } finally {
  138. supabaseClient.dispose();
  139. await File('./uploaded/raw').delete(recursive: true);
  140. await File('./uploaded/data').delete(recursive: true);
  141. }
  142. });
  143. return router;
  144. }
  145. }