@oxyhq/services 5.10.4 → 5.10.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/README.md +62 -14
  2. package/lib/commonjs/core/OxyServices.js +797 -5
  3. package/lib/commonjs/core/OxyServices.js.map +1 -1
  4. package/lib/commonjs/core/index.js +8 -83
  5. package/lib/commonjs/core/index.js.map +1 -1
  6. package/lib/commonjs/index.js +31 -1
  7. package/lib/commonjs/index.js.map +1 -1
  8. package/lib/commonjs/ui/screens/FileManagementScreen.js +12 -12
  9. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  10. package/lib/commonjs/ui/screens/ProfileScreen.js +2 -2
  11. package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
  12. package/lib/commonjs/ui/screens/SignInScreen.js +1 -1
  13. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  14. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js +1 -1
  15. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
  16. package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js +1 -1
  17. package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js.map +1 -1
  18. package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js +1 -1
  19. package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js.map +1 -1
  20. package/lib/commonjs/ui/stores/followStore.js +4 -4
  21. package/lib/commonjs/ui/stores/followStore.js.map +1 -1
  22. package/lib/commonjs/utils/s3FileManager.js +243 -0
  23. package/lib/commonjs/utils/s3FileManager.js.map +1 -0
  24. package/lib/commonjs/utils/s3FileManagerExample.js +407 -0
  25. package/lib/commonjs/utils/s3FileManagerExample.js.map +1 -0
  26. package/lib/commonjs/utils/s3FileManagerRN.js +274 -0
  27. package/lib/commonjs/utils/s3FileManagerRN.js.map +1 -0
  28. package/lib/module/core/OxyServices.js +796 -4
  29. package/lib/module/core/OxyServices.js.map +1 -1
  30. package/lib/module/core/index.js +9 -24
  31. package/lib/module/core/index.js.map +1 -1
  32. package/lib/module/index.js +4 -0
  33. package/lib/module/index.js.map +1 -1
  34. package/lib/module/ui/screens/FileManagementScreen.js +12 -12
  35. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  36. package/lib/module/ui/screens/ProfileScreen.js +2 -2
  37. package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
  38. package/lib/module/ui/screens/SignInScreen.js +1 -1
  39. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  40. package/lib/module/ui/screens/karma/KarmaCenterScreen.js +1 -1
  41. package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
  42. package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js +1 -1
  43. package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js.map +1 -1
  44. package/lib/module/ui/screens/karma/KarmaRulesScreen.js +1 -1
  45. package/lib/module/ui/screens/karma/KarmaRulesScreen.js.map +1 -1
  46. package/lib/module/ui/stores/followStore.js +4 -4
  47. package/lib/module/ui/stores/followStore.js.map +1 -1
  48. package/lib/module/utils/s3FileManager.js +237 -0
  49. package/lib/module/utils/s3FileManager.js.map +1 -0
  50. package/lib/module/utils/s3FileManagerExample.js +400 -0
  51. package/lib/module/utils/s3FileManagerExample.js.map +1 -0
  52. package/lib/module/utils/s3FileManagerRN.js +268 -0
  53. package/lib/module/utils/s3FileManagerRN.js.map +1 -0
  54. package/lib/typescript/core/OxyServices.d.ts +292 -3
  55. package/lib/typescript/core/OxyServices.d.ts.map +1 -1
  56. package/lib/typescript/core/index.d.ts +7 -16
  57. package/lib/typescript/core/index.d.ts.map +1 -1
  58. package/lib/typescript/index.d.ts +4 -0
  59. package/lib/typescript/index.d.ts.map +1 -1
  60. package/lib/typescript/utils/s3FileManager.d.ts +81 -0
  61. package/lib/typescript/utils/s3FileManager.d.ts.map +1 -0
  62. package/lib/typescript/utils/s3FileManagerExample.d.ts +87 -0
  63. package/lib/typescript/utils/s3FileManagerExample.d.ts.map +1 -0
  64. package/lib/typescript/utils/s3FileManagerRN.d.ts +104 -0
  65. package/lib/typescript/utils/s3FileManagerRN.d.ts.map +1 -0
  66. package/package.json +3 -1
  67. package/src/core/OxyServices.ts +881 -46
  68. package/src/core/index.ts +9 -24
  69. package/src/index.ts +17 -1
  70. package/src/ui/screens/FileManagementScreen.tsx +12 -12
  71. package/src/ui/screens/ProfileScreen.tsx +3 -3
  72. package/src/ui/screens/SignInScreen.tsx +1 -1
  73. package/src/ui/screens/karma/KarmaCenterScreen.tsx +2 -2
  74. package/src/ui/screens/karma/KarmaLeaderboardScreen.tsx +1 -1
  75. package/src/ui/screens/karma/KarmaRulesScreen.tsx +1 -1
  76. package/src/ui/stores/followStore.ts +4 -4
  77. package/src/utils/s3FileManager.ts +281 -0
  78. package/src/utils/s3FileManagerExample.ts +432 -0
  79. package/src/utils/s3FileManagerRN.ts +322 -0
  80. package/lib/commonjs/core/OxyServicesMain.js +0 -51
  81. package/lib/commonjs/core/OxyServicesMain.js.map +0 -1
  82. package/lib/commonjs/core/analytics/AnalyticsService.js +0 -67
  83. package/lib/commonjs/core/analytics/AnalyticsService.js.map +0 -1
  84. package/lib/commonjs/core/auth/AuthService.js +0 -538
  85. package/lib/commonjs/core/auth/AuthService.js.map +0 -1
  86. package/lib/commonjs/core/devices/DeviceService.js +0 -61
  87. package/lib/commonjs/core/devices/DeviceService.js.map +0 -1
  88. package/lib/commonjs/core/files/FileService.js +0 -180
  89. package/lib/commonjs/core/files/FileService.js.map +0 -1
  90. package/lib/commonjs/core/karma/KarmaService.js +0 -100
  91. package/lib/commonjs/core/karma/KarmaService.js.map +0 -1
  92. package/lib/commonjs/core/locations/LocationService.js +0 -131
  93. package/lib/commonjs/core/locations/LocationService.js.map +0 -1
  94. package/lib/commonjs/core/payments/PaymentService.js +0 -124
  95. package/lib/commonjs/core/payments/PaymentService.js.map +0 -1
  96. package/lib/commonjs/core/users/UserService.js +0 -234
  97. package/lib/commonjs/core/users/UserService.js.map +0 -1
  98. package/lib/module/core/OxyServicesMain.js +0 -47
  99. package/lib/module/core/OxyServicesMain.js.map +0 -1
  100. package/lib/module/core/analytics/AnalyticsService.js +0 -62
  101. package/lib/module/core/analytics/AnalyticsService.js.map +0 -1
  102. package/lib/module/core/auth/AuthService.js +0 -533
  103. package/lib/module/core/auth/AuthService.js.map +0 -1
  104. package/lib/module/core/devices/DeviceService.js +0 -57
  105. package/lib/module/core/devices/DeviceService.js.map +0 -1
  106. package/lib/module/core/files/FileService.js +0 -175
  107. package/lib/module/core/files/FileService.js.map +0 -1
  108. package/lib/module/core/karma/KarmaService.js +0 -95
  109. package/lib/module/core/karma/KarmaService.js.map +0 -1
  110. package/lib/module/core/locations/LocationService.js +0 -127
  111. package/lib/module/core/locations/LocationService.js.map +0 -1
  112. package/lib/module/core/payments/PaymentService.js +0 -119
  113. package/lib/module/core/payments/PaymentService.js.map +0 -1
  114. package/lib/module/core/users/UserService.js +0 -230
  115. package/lib/module/core/users/UserService.js.map +0 -1
  116. package/lib/typescript/core/OxyServicesMain.d.ts +0 -33
  117. package/lib/typescript/core/OxyServicesMain.d.ts.map +0 -1
  118. package/lib/typescript/core/analytics/AnalyticsService.d.ts +0 -26
  119. package/lib/typescript/core/analytics/AnalyticsService.d.ts.map +0 -1
  120. package/lib/typescript/core/auth/AuthService.d.ts +0 -165
  121. package/lib/typescript/core/auth/AuthService.d.ts.map +0 -1
  122. package/lib/typescript/core/devices/DeviceService.d.ts +0 -20
  123. package/lib/typescript/core/devices/DeviceService.d.ts.map +0 -1
  124. package/lib/typescript/core/files/FileService.d.ts +0 -59
  125. package/lib/typescript/core/files/FileService.d.ts.map +0 -1
  126. package/lib/typescript/core/karma/KarmaService.d.ts +0 -50
  127. package/lib/typescript/core/karma/KarmaService.d.ts.map +0 -1
  128. package/lib/typescript/core/locations/LocationService.d.ts +0 -39
  129. package/lib/typescript/core/locations/LocationService.d.ts.map +0 -1
  130. package/lib/typescript/core/payments/PaymentService.d.ts +0 -50
  131. package/lib/typescript/core/payments/PaymentService.d.ts.map +0 -1
  132. package/lib/typescript/core/users/UserService.d.ts +0 -111
  133. package/lib/typescript/core/users/UserService.d.ts.map +0 -1
  134. package/src/core/OxyServicesMain.ts +0 -57
  135. package/src/core/analytics/AnalyticsService.ts +0 -64
  136. package/src/core/auth/AuthService.ts +0 -560
  137. package/src/core/devices/DeviceService.ts +0 -55
  138. package/src/core/files/FileService.ts +0 -198
  139. package/src/core/karma/KarmaService.ts +0 -104
  140. package/src/core/locations/LocationService.ts +0 -141
  141. package/src/core/payments/PaymentService.ts +0 -133
  142. package/src/core/users/UserService.ts +0 -241
package/src/core/index.ts CHANGED
@@ -1,28 +1,13 @@
1
1
  /**
2
- * OxyServices Core Module - Modular Architecture
2
+ * OxyServices Core Module - Unified Architecture
3
3
  *
4
- * This module exports the main OxyServices class and all individual service modules
5
- * for a clean, maintainable, and focused architecture.
4
+ * This module exports the unified OxyServices class that provides all API functionality
5
+ * in one simple, easy-to-use interface.
6
6
  */
7
7
 
8
- // Main OxyServices class (backward compatible)
9
- export { OxyServicesMain as OxyServices } from './OxyServicesMain';
10
-
11
- // Individual service classes for focused usage
12
- export { AuthService } from './auth/AuthService';
13
- export { UserService } from './users/UserService';
14
- export { PaymentService } from './payments/PaymentService';
15
- export { KarmaService } from './karma/KarmaService';
16
- export { FileService } from './files/FileService';
17
- export { LocationService } from './locations/LocationService';
18
- export { AnalyticsService } from './analytics/AnalyticsService';
19
- export { DeviceService } from './devices/DeviceService';
20
-
21
- // Base class for custom service extensions
22
- export { OxyServices as BaseOxyServices } from './OxyServices';
23
-
24
- // Constants
25
- export { OXY_CLOUD_URL } from './files/FileService';
8
+ // Main OxyServices class (unified)
9
+ export { OxyServices } from './OxyServices';
10
+ export { OXY_CLOUD_URL } from './OxyServices';
26
11
 
27
12
  // Re-export all models and types for convenience
28
13
  export * from '../models/interfaces';
@@ -32,8 +17,8 @@ export * from '../models/session';
32
17
  export { DeviceManager } from '../utils/deviceManager';
33
18
  export type { DeviceFingerprint, StoredDeviceInfo } from '../utils/deviceManager';
34
19
 
35
- // Import the main class for default export
36
- import { OxyServicesMain } from './OxyServicesMain';
20
+ // Import for default export
21
+ import { OxyServices } from './OxyServices';
37
22
 
38
23
  // Default export for backward compatibility
39
- export default OxyServicesMain;
24
+ export default OxyServices;
package/src/index.ts CHANGED
@@ -94,4 +94,20 @@ export {
94
94
  logPerformance
95
95
  } from './utils/loggerUtils';
96
96
  export * from './utils/asyncUtils';
97
- export * from './utils/hookUtils';
97
+ export * from './utils/hookUtils';
98
+
99
+ // S3 File Management exports
100
+ export {
101
+ S3FileManager,
102
+ createS3FileManager
103
+ } from './utils/s3FileManager';
104
+ export {
105
+ S3FileManagerRN,
106
+ createS3FileManagerRN
107
+ } from './utils/s3FileManagerRN';
108
+ export type {
109
+ S3Config,
110
+ UploadOptions,
111
+ FileInfo
112
+ } from './utils/s3FileManager';
113
+ export type { RNFile } from './utils/s3FileManagerRN';
@@ -118,7 +118,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
118
118
  setLoading(true);
119
119
  }
120
120
 
121
- const response = await oxyServices.files.listUserFiles(targetUserId);
121
+ const response = await oxyServices.listUserFiles(targetUserId);
122
122
  setFiles(response.files || []);
123
123
  } catch (error: any) {
124
124
  console.error('Failed to load files:', error);
@@ -172,7 +172,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
172
172
  await Promise.all(
173
173
  photosToLoad.map(async (photo) => {
174
174
  try {
175
- const downloadUrl = oxyServices.files.getFileDownloadUrl(photo.id);
175
+ const downloadUrl = oxyServices.getFileDownloadUrl(photo.id);
176
176
 
177
177
  if (Platform.OS === 'web') {
178
178
  const img = new (window as any).Image();
@@ -340,7 +340,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
340
340
  console.log('Current user ID:', user?.id);
341
341
  setDeleting(fileId);
342
342
 
343
- const result = await oxyServices.files.deleteFile(fileId);
343
+ const result = await oxyServices.deleteFile(fileId);
344
344
  console.log('Delete result:', result);
345
345
 
346
346
  toast.success('File deleted successfully');
@@ -408,7 +408,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
408
408
  console.log('Downloading file:', { fileId, filename });
409
409
 
410
410
  // Use the public download URL method
411
- const downloadUrl = oxyServices.files.getFileDownloadUrl(fileId);
411
+ const downloadUrl = oxyServices.getFileDownloadUrl(fileId);
412
412
  console.log('Download URL:', downloadUrl);
413
413
 
414
414
  try {
@@ -426,7 +426,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
426
426
  console.warn('Link download failed, trying fetch method:', linkError);
427
427
 
428
428
  // Method 2: Fallback to authenticated download
429
- const blob = await oxyServices.files.getFileContentAsBlob(fileId);
429
+ const blob = await oxyServices.getFileContentAsBlob(fileId);
430
430
  const url = window.URL.createObjectURL(blob);
431
431
 
432
432
  const link = document.createElement('a');
@@ -491,11 +491,11 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
491
491
  file.contentType.startsWith('video/') ||
492
492
  file.contentType.startsWith('audio/')) {
493
493
  // For images, PDFs, videos, and audio, we'll use the URL directly
494
- const downloadUrl = oxyServices.files.getFileDownloadUrl(file.id);
494
+ const downloadUrl = oxyServices.getFileDownloadUrl(file.id);
495
495
  setFileContent(downloadUrl);
496
496
  } else {
497
497
  // For text files, get the content using authenticated request
498
- const content = await oxyServices.files.getFileContentAsText(file.id);
498
+ const content = await oxyServices.getFileContentAsText(file.id);
499
499
  setFileContent(content);
500
500
  }
501
501
  } catch (error: any) {
@@ -532,7 +532,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
532
532
  };
533
533
 
534
534
  const renderSimplePhotoItem = useCallback((photo: FileMetadata, index: number) => {
535
- const downloadUrl = oxyServices.files.getFileDownloadUrl(photo.id);
535
+ const downloadUrl = oxyServices.getFileDownloadUrl(photo.id);
536
536
 
537
537
  // Calculate photo item width based on actual container size from bottom sheet
538
538
  let itemsPerRow = 3; // Default for mobile
@@ -598,7 +598,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
598
598
  }, [oxyServices, containerWidth]);
599
599
 
600
600
  const renderJustifiedPhotoItem = useCallback((photo: FileMetadata, width: number, height: number, isLast: boolean) => {
601
- const downloadUrl = oxyServices.files.getFileDownloadUrl(photo.id);
601
+ const downloadUrl = oxyServices.getFileDownloadUrl(photo.id);
602
602
 
603
603
  return (
604
604
  <TouchableOpacity
@@ -689,7 +689,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
689
689
  {isImage && (
690
690
  Platform.OS === 'web' ? (
691
691
  <img
692
- src={oxyServices.files.getFileDownloadUrl(file.id)}
692
+ src={oxyServices.getFileDownloadUrl(file.id)}
693
693
  style={{
694
694
  width: '100%',
695
695
  height: '100%',
@@ -709,7 +709,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
709
709
  />
710
710
  ) : (
711
711
  <Image
712
- source={{ uri: oxyServices.files.getFileDownloadUrl(file.id) }}
712
+ source={{ uri: oxyServices.getFileDownloadUrl(file.id) }}
713
713
  style={styles.previewImage}
714
714
  resizeMode="cover"
715
715
  onError={() => {
@@ -817,7 +817,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
817
817
  };
818
818
 
819
819
  const renderPhotoItem = (photo: FileMetadata, index: number) => {
820
- const downloadUrl = oxyServices.files.getFileDownloadUrl(photo.id);
820
+ const downloadUrl = oxyServices.getFileDownloadUrl(photo.id);
821
821
 
822
822
  // Calculate photo item width based on actual container size from bottom sheet
823
823
  let itemsPerRow = 3; // Default for mobile
@@ -61,7 +61,7 @@ const ProfileScreen: React.FC<ProfileScreenProps> = ({ userId, username, theme,
61
61
 
62
62
  // Load user profile and karma total
63
63
  Promise.all([
64
- oxyServices.users.getUserById(userId).catch((err: any) => {
64
+ oxyServices.getUserById(userId).catch((err: any) => {
65
65
  console.error('getUserById error:', err);
66
66
  // If this is the current user and the API call fails, use current user data as fallback
67
67
  if (currentUser && currentUser.id === userId) {
@@ -70,8 +70,8 @@ const ProfileScreen: React.FC<ProfileScreenProps> = ({ userId, username, theme,
70
70
  }
71
71
  throw err;
72
72
  }),
73
- oxyServices.karma.getUserKarmaTotal ?
74
- oxyServices.karma.getUserKarmaTotal(userId).catch((err: any) => {
73
+ oxyServices.getUserKarmaTotal ?
74
+ oxyServices.getUserKarmaTotal(userId).catch((err: any) => {
75
75
  console.warn('getUserKarmaTotal error:', err);
76
76
  return { total: undefined };
77
77
  }) :
@@ -136,7 +136,7 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
136
136
 
137
137
  try {
138
138
  // First check if username exists by trying to get profile
139
- const profile = await oxyServices.users.getProfileByUsername(usernameToValidate);
139
+ const profile = await oxyServices.getProfileByUsername(usernameToValidate);
140
140
 
141
141
  if (profile) {
142
142
  const profileData = {
@@ -38,8 +38,8 @@ const KarmaCenterScreen: React.FC<BaseScreenProps> = ({
38
38
  setIsLoading(true);
39
39
  setError(null);
40
40
  Promise.all([
41
- oxyServices.karma.getUserKarmaTotal(user.id),
42
- oxyServices.karma.getUserKarmaHistory(user.id, 20, 0),
41
+ oxyServices.getUserKarmaTotal(user.id),
42
+ oxyServices.getUserKarmaHistory(user.id, 20, 0),
43
43
  ])
44
44
  .then(([totalRes, historyRes]) => {
45
45
  setKarmaTotal(totalRes.total);
@@ -19,7 +19,7 @@ const KarmaLeaderboardScreen: React.FC<BaseScreenProps> = ({ goBack, theme, navi
19
19
  useEffect(() => {
20
20
  setIsLoading(true);
21
21
  setError(null);
22
- oxyServices.karma.getKarmaLeaderboard()
22
+ oxyServices.getKarmaLeaderboard()
23
23
  .then((data: any) => setLeaderboard(Array.isArray(data) ? data : []))
24
24
  .catch((err: any) => setError(err.message || 'Failed to load leaderboard'))
25
25
  .finally(() => setIsLoading(false));
@@ -18,7 +18,7 @@ const KarmaRulesScreen: React.FC<BaseScreenProps> = ({ goBack, theme }) => {
18
18
  useEffect(() => {
19
19
  setIsLoading(true);
20
20
  setError(null);
21
- oxyServices.karma.getKarmaRules()
21
+ oxyServices.getKarmaRules()
22
22
  .then((data: any) => setRules(Array.isArray(data) ? data : []))
23
23
  .catch((err: any) => setError(err.message || 'Failed to load rules'))
24
24
  .finally(() => setIsLoading(false));
@@ -53,7 +53,7 @@ export const useFollowStore = create<FollowState>((set: any, get: any) => ({
53
53
  errors: { ...state.errors, [userId]: null },
54
54
  }));
55
55
  try {
56
- const response = await oxyServices.users.getFollowStatus(userId);
56
+ const response = await oxyServices.getFollowStatus(userId);
57
57
  set((state: FollowState) => ({
58
58
  followingUsers: { ...state.followingUsers, [userId]: response.isFollowing },
59
59
  fetchingUsers: { ...state.fetchingUsers, [userId]: false },
@@ -75,10 +75,10 @@ export const useFollowStore = create<FollowState>((set: any, get: any) => ({
75
75
  let response: any;
76
76
  let newFollowState;
77
77
  if (isCurrentlyFollowing) {
78
- response = await oxyServices.users.unfollowUser(userId);
78
+ response = await oxyServices.unfollowUser(userId);
79
79
  newFollowState = false;
80
80
  } else {
81
- response = await oxyServices.users.followUser(userId);
81
+ response = await oxyServices.followUser(userId);
82
82
  newFollowState = true;
83
83
  }
84
84
 
@@ -158,7 +158,7 @@ export const useFollowStore = create<FollowState>((set: any, get: any) => ({
158
158
  loadingCounts: { ...state.loadingCounts, [userId]: true },
159
159
  }));
160
160
  try {
161
- const user = await oxyServices.users.getUserById(userId);
161
+ const user = await oxyServices.getUserById(userId);
162
162
  if (user && user._count) {
163
163
  set((state: FollowState) => ({
164
164
  followerCounts: {
@@ -0,0 +1,281 @@
1
+ import { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand, ListObjectsV2Command } from '@aws-sdk/client-s3';
2
+ import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
3
+
4
+ export interface S3Config {
5
+ region: string;
6
+ accessKeyId: string;
7
+ secretAccessKey: string;
8
+ bucketName: string;
9
+ }
10
+
11
+ export interface UploadOptions {
12
+ contentType?: string;
13
+ metadata?: Record<string, string>;
14
+ publicRead?: boolean;
15
+ }
16
+
17
+ export interface FileInfo {
18
+ key: string;
19
+ size: number;
20
+ lastModified: Date;
21
+ contentType?: string;
22
+ metadata?: Record<string, string>;
23
+ }
24
+
25
+ export class S3FileManager {
26
+ private s3Client: S3Client;
27
+ private bucketName: string;
28
+
29
+ constructor(config: S3Config) {
30
+ this.s3Client = new S3Client({
31
+ region: config.region,
32
+ credentials: {
33
+ accessKeyId: config.accessKeyId,
34
+ secretAccessKey: config.secretAccessKey,
35
+ },
36
+ });
37
+ this.bucketName = config.bucketName;
38
+ }
39
+
40
+ /**
41
+ * Upload a file to S3
42
+ */
43
+ async uploadFile(
44
+ key: string,
45
+ file: File | Buffer | string,
46
+ options: UploadOptions = {}
47
+ ): Promise<string> {
48
+ let body: Buffer | string;
49
+ let contentType = options.contentType;
50
+
51
+ if (file instanceof File) {
52
+ body = await this.fileToBuffer(file);
53
+ contentType = contentType || file.type;
54
+ } else if (typeof file === 'string') {
55
+ body = file;
56
+ contentType = contentType || 'text/plain';
57
+ } else {
58
+ body = file;
59
+ contentType = contentType || 'application/octet-stream';
60
+ }
61
+
62
+ const command = new PutObjectCommand({
63
+ Bucket: this.bucketName,
64
+ Key: key,
65
+ Body: body,
66
+ ContentType: contentType,
67
+ Metadata: options.metadata,
68
+ ACL: options.publicRead ? 'public-read' : 'private',
69
+ });
70
+
71
+ await this.s3Client.send(command);
72
+ return `https://${this.bucketName}.s3.amazonaws.com/${key}`;
73
+ }
74
+
75
+ /**
76
+ * Download a file from S3
77
+ */
78
+ async downloadFile(key: string): Promise<Buffer> {
79
+ const command = new GetObjectCommand({
80
+ Bucket: this.bucketName,
81
+ Key: key,
82
+ });
83
+
84
+ const response = await this.s3Client.send(command);
85
+
86
+ if (!response.Body) {
87
+ throw new Error('File not found or empty');
88
+ }
89
+
90
+ // Convert stream to buffer
91
+ const chunks: Uint8Array[] = [];
92
+ const reader = response.Body.transformToWebStream().getReader();
93
+
94
+ while (true) {
95
+ const { done, value } = await reader.read();
96
+ if (done) break;
97
+ chunks.push(value);
98
+ }
99
+
100
+ return Buffer.concat(chunks);
101
+ }
102
+
103
+ /**
104
+ * Delete a file from S3
105
+ */
106
+ async deleteFile(key: string): Promise<void> {
107
+ const command = new DeleteObjectCommand({
108
+ Bucket: this.bucketName,
109
+ Key: key,
110
+ });
111
+
112
+ await this.s3Client.send(command);
113
+ }
114
+
115
+ /**
116
+ * Generate a presigned URL for file upload
117
+ */
118
+ async getPresignedUploadUrl(
119
+ key: string,
120
+ contentType: string,
121
+ expiresIn: number = 3600
122
+ ): Promise<string> {
123
+ const command = new PutObjectCommand({
124
+ Bucket: this.bucketName,
125
+ Key: key,
126
+ ContentType: contentType,
127
+ });
128
+
129
+ return getSignedUrl(this.s3Client, command, { expiresIn });
130
+ }
131
+
132
+ /**
133
+ * Generate a presigned URL for file download
134
+ */
135
+ async getPresignedDownloadUrl(
136
+ key: string,
137
+ expiresIn: number = 3600
138
+ ): Promise<string> {
139
+ const command = new GetObjectCommand({
140
+ Bucket: this.bucketName,
141
+ Key: key,
142
+ });
143
+
144
+ return getSignedUrl(this.s3Client, command, { expiresIn });
145
+ }
146
+
147
+ /**
148
+ * List files in a directory
149
+ */
150
+ async listFiles(prefix: string = '', maxKeys: number = 1000): Promise<FileInfo[]> {
151
+ const command = new ListObjectsV2Command({
152
+ Bucket: this.bucketName,
153
+ Prefix: prefix,
154
+ MaxKeys: maxKeys,
155
+ });
156
+
157
+ const response = await this.s3Client.send(command);
158
+
159
+ if (!response.Contents) {
160
+ return [];
161
+ }
162
+
163
+ return response.Contents.map((item) => ({
164
+ key: item.Key!,
165
+ size: item.Size || 0,
166
+ lastModified: item.LastModified!,
167
+ }));
168
+ }
169
+
170
+ /**
171
+ * Check if a file exists
172
+ */
173
+ async fileExists(key: string): Promise<boolean> {
174
+ try {
175
+ const command = new GetObjectCommand({
176
+ Bucket: this.bucketName,
177
+ Key: key,
178
+ });
179
+
180
+ await this.s3Client.send(command);
181
+ return true;
182
+ } catch (error: any) {
183
+ if (error.name === 'NoSuchKey') {
184
+ return false;
185
+ }
186
+ throw error;
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Get file metadata
192
+ */
193
+ async getFileMetadata(key: string): Promise<FileInfo | null> {
194
+ try {
195
+ const command = new GetObjectCommand({
196
+ Bucket: this.bucketName,
197
+ Key: key,
198
+ });
199
+
200
+ const response = await this.s3Client.send(command);
201
+
202
+ return {
203
+ key,
204
+ size: parseInt(response.ContentLength?.toString() || '0'),
205
+ lastModified: response.LastModified!,
206
+ contentType: response.ContentType,
207
+ metadata: response.Metadata,
208
+ };
209
+ } catch (error: any) {
210
+ if (error.name === 'NoSuchKey') {
211
+ return null;
212
+ }
213
+ throw error;
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Convert File object to Buffer (for React Native compatibility)
219
+ */
220
+ private async fileToBuffer(file: File): Promise<Buffer> {
221
+ return new Promise((resolve, reject) => {
222
+ const reader = new FileReader();
223
+ reader.onload = () => {
224
+ if (reader.result instanceof ArrayBuffer) {
225
+ resolve(Buffer.from(reader.result));
226
+ } else {
227
+ reject(new Error('Failed to read file'));
228
+ }
229
+ };
230
+ reader.onerror = () => reject(reader.error);
231
+ reader.readAsArrayBuffer(file);
232
+ });
233
+ }
234
+
235
+ /**
236
+ * Upload multiple files
237
+ */
238
+ async uploadMultipleFiles(
239
+ files: Array<{ key: string; file: File | Buffer | string; options?: UploadOptions }>
240
+ ): Promise<string[]> {
241
+ const uploadPromises = files.map(({ key, file, options }) =>
242
+ this.uploadFile(key, file, options)
243
+ );
244
+
245
+ return Promise.all(uploadPromises);
246
+ }
247
+
248
+ /**
249
+ * Delete multiple files
250
+ */
251
+ async deleteMultipleFiles(keys: string[]): Promise<void> {
252
+ const deletePromises = keys.map(key => this.deleteFile(key));
253
+ await Promise.all(deletePromises);
254
+ }
255
+
256
+ /**
257
+ * Copy file from one key to another
258
+ */
259
+ async copyFile(sourceKey: string, destinationKey: string): Promise<void> {
260
+ const command = new PutObjectCommand({
261
+ Bucket: this.bucketName,
262
+ Key: destinationKey,
263
+ Body: await this.downloadFile(sourceKey),
264
+ });
265
+
266
+ await this.s3Client.send(command);
267
+ }
268
+
269
+ /**
270
+ * Move file (copy + delete)
271
+ */
272
+ async moveFile(sourceKey: string, destinationKey: string): Promise<void> {
273
+ await this.copyFile(sourceKey, destinationKey);
274
+ await this.deleteFile(sourceKey);
275
+ }
276
+ }
277
+
278
+ // Export a factory function for easier configuration
279
+ export function createS3FileManager(config: S3Config): S3FileManager {
280
+ return new S3FileManager(config);
281
+ }