@things-factory/integration-sftp 4.3.660 → 4.3.668

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 (68) hide show
  1. package/CHANGELOG.md +369 -0
  2. package/dist-server/controllers/herbalife/herbalife.js +9 -5
  3. package/dist-server/controllers/herbalife/herbalife.js.map +1 -1
  4. package/dist-server/controllers/index.js +1 -0
  5. package/dist-server/controllers/index.js.map +1 -1
  6. package/dist-server/controllers/sftp-api/index.js +7 -0
  7. package/dist-server/controllers/sftp-api/index.js.map +1 -1
  8. package/dist-server/controllers/yltc/apis/create-yltc-inventory-report.js +40 -0
  9. package/dist-server/controllers/yltc/apis/create-yltc-inventory-report.js.map +1 -0
  10. package/dist-server/controllers/yltc/apis/echo.js +19 -0
  11. package/dist-server/controllers/yltc/apis/echo.js.map +1 -0
  12. package/dist-server/controllers/yltc/apis/index.js +19 -0
  13. package/dist-server/controllers/yltc/apis/index.js.map +1 -0
  14. package/dist-server/controllers/yltc/index.js +34 -0
  15. package/dist-server/controllers/yltc/index.js.map +1 -0
  16. package/dist-server/controllers/yltc/platform-action.js +30 -0
  17. package/dist-server/controllers/yltc/platform-action.js.map +1 -0
  18. package/dist-server/controllers/yltc/yltc.js +71 -0
  19. package/dist-server/controllers/yltc/yltc.js.map +1 -0
  20. package/dist-server/service/sftp/sftp-mutation.js +15 -8
  21. package/dist-server/service/sftp/sftp-mutation.js.map +1 -1
  22. package/dist-server/service/sftp/sftp.js +41 -0
  23. package/dist-server/service/sftp/sftp.js.map +1 -1
  24. package/dist-server/sftp-const.js +36 -2
  25. package/dist-server/sftp-const.js.map +1 -1
  26. package/dist-server/storage/providers/ftp-storage.provider.js +178 -0
  27. package/dist-server/storage/providers/ftp-storage.provider.js.map +1 -0
  28. package/dist-server/storage/providers/local-storage.provider.js +153 -0
  29. package/dist-server/storage/providers/local-storage.provider.js.map +1 -0
  30. package/dist-server/storage/providers/s3-storage.provider.js +181 -0
  31. package/dist-server/storage/providers/s3-storage.provider.js.map +1 -0
  32. package/dist-server/storage/providers/sftp-storage.provider.js +133 -0
  33. package/dist-server/storage/providers/sftp-storage.provider.js.map +1 -0
  34. package/dist-server/storage/storage-factory.js +55 -0
  35. package/dist-server/storage/storage-factory.js.map +1 -0
  36. package/dist-server/storage/storage-manager.js +86 -0
  37. package/dist-server/storage/storage-manager.js.map +1 -0
  38. package/dist-server/storage/storage-provider.interface.js +3 -0
  39. package/dist-server/storage/storage-provider.interface.js.map +1 -0
  40. package/dist-server/util/file-formatters.js +100 -0
  41. package/dist-server/util/file-formatters.js.map +1 -0
  42. package/dist-server/util/generate-files.js +77 -17
  43. package/dist-server/util/generate-files.js.map +1 -1
  44. package/dist-server/util/get-permitted-directories.js +9 -5
  45. package/dist-server/util/get-permitted-directories.js.map +1 -1
  46. package/package.json +6 -3
  47. package/server/controllers/herbalife/herbalife.ts +11 -6
  48. package/server/controllers/index.ts +1 -0
  49. package/server/controllers/sftp-api/index.ts +3 -0
  50. package/server/controllers/yltc/apis/create-yltc-inventory-report.ts +37 -0
  51. package/server/controllers/yltc/apis/echo.ts +14 -0
  52. package/server/controllers/yltc/apis/index.ts +2 -0
  53. package/server/controllers/yltc/index.ts +7 -0
  54. package/server/controllers/yltc/platform-action.ts +34 -0
  55. package/server/controllers/yltc/yltc.ts +93 -0
  56. package/server/service/sftp/sftp-mutation.ts +16 -10
  57. package/server/service/sftp/sftp.ts +34 -0
  58. package/server/sftp-const.ts +41 -1
  59. package/server/storage/providers/ftp-storage.provider.ts +177 -0
  60. package/server/storage/providers/local-storage.provider.ts +159 -0
  61. package/server/storage/providers/s3-storage.provider.ts +214 -0
  62. package/server/storage/providers/sftp-storage.provider.ts +157 -0
  63. package/server/storage/storage-factory.ts +62 -0
  64. package/server/storage/storage-manager.ts +103 -0
  65. package/server/storage/storage-provider.interface.ts +42 -0
  66. package/server/util/file-formatters.ts +97 -0
  67. package/server/util/generate-files.ts +79 -17
  68. package/server/util/get-permitted-directories.ts +11 -7
@@ -60,6 +60,22 @@ export class Sftp {
60
60
  @Field({ nullable: true })
61
61
  username?: string
62
62
 
63
+ @Column({ nullable: true })
64
+ @Field({ nullable: true })
65
+ schedule?: string
66
+
67
+ // Store password securely; not exposed via GraphQL Field decorator
68
+ @Column({ nullable: true })
69
+ password?: string
70
+
71
+ @Column({ nullable: true })
72
+ @Field({ nullable: true })
73
+ host?: string
74
+
75
+ @Column({ type: 'int', nullable: true })
76
+ @Field({ nullable: true })
77
+ port?: number
78
+
63
79
  @Column({
64
80
  nullable: true
65
81
  })
@@ -78,6 +94,10 @@ export class Sftp {
78
94
  @Field({ nullable: true })
79
95
  folderPath?: string
80
96
 
97
+ @Column({ nullable: true })
98
+ @Field({ nullable: true })
99
+ basePath?: string
100
+
81
101
  @Column({
82
102
  nullable: true
83
103
  })
@@ -111,6 +131,20 @@ export class Sftp {
111
131
  @Field({ nullable: true })
112
132
  responseFileTypes: string
113
133
 
134
+ @Column({
135
+ nullable: true
136
+ })
137
+ @Field({ nullable: true })
138
+ sftpType: string
139
+
140
+ @Column({ type: 'int', nullable: true })
141
+ @Field({ nullable: true })
142
+ timeout?: number
143
+
144
+ @Column({ type: 'int', nullable: true })
145
+ @Field({ nullable: true })
146
+ retries?: number
147
+
114
148
  @Column({ nullable: true })
115
149
  @Field({ nullable: true })
116
150
  lastTimeSync?: Date
@@ -1,6 +1,8 @@
1
1
  import { config } from '@things-factory/env'
2
+ import { StorageManager } from './storage/storage-manager'
3
+ import { StorageConfig } from './storage/storage-provider.interface'
2
4
 
3
- export var SFTPFILESTORAGE: any = config.get('sftpFileStorage')
5
+ // Path constants
4
6
  export var SUBMITDATAPATH: string = '/sob/data'
5
7
  export var SUCCESSDATAPATH: string = '/sob/success'
6
8
  export var FAILEDDATAPATH: string = '/sob/failed'
@@ -11,3 +13,41 @@ export var SNSUBMITDATAPATH: string = '/sn/data'
11
13
  export var SNSUCCESSDATAPATH: string = '/sn/success'
12
14
  export var SNFAILEDDATAPATH: string = '/sn/failed'
13
15
  export var BACKUPPATH: string = 'hatiosea/'
16
+
17
+ // Storage configuration helper
18
+ export function getSftpStorageConfig(): StorageConfig {
19
+ return config.get('sftpFileStorage')
20
+ }
21
+
22
+ // Legacy compatibility - deprecated, use StorageManager instead
23
+ export var SFTPFILESTORAGE: any = {
24
+ // This is kept for backward compatibility but should not be used in new code
25
+ // Use StorageManager.getInstance() instead
26
+ get config() {
27
+ return getSftpStorageConfig()
28
+ },
29
+
30
+ async readFile(path: string, encoding?: string) {
31
+ const storageManager = StorageManager.getInstance()
32
+ const storageConfig = getSftpStorageConfig()
33
+ return await storageManager.readFile(storageConfig, path, encoding)
34
+ },
35
+
36
+ async uploadFile(params: { stream: any; filename: string; uploadPath: string }) {
37
+ const storageManager = StorageManager.getInstance()
38
+ const storageConfig = getSftpStorageConfig()
39
+ return await storageManager.uploadFile(storageConfig, params)
40
+ },
41
+
42
+ async readFolders(params: { path: string }) {
43
+ const storageManager = StorageManager.getInstance()
44
+ const storageConfig = getSftpStorageConfig()
45
+ return await storageManager.readFolders(storageConfig, params)
46
+ },
47
+
48
+ async moveFile(params: { source: string; destination: string }) {
49
+ const storageManager = StorageManager.getInstance()
50
+ const storageConfig = getSftpStorageConfig()
51
+ return await storageManager.moveFile(storageConfig, params)
52
+ }
53
+ }
@@ -0,0 +1,177 @@
1
+ import * as ftp from 'basic-ftp'
2
+ import { FileType } from 'basic-ftp'
3
+ import { StorageProvider, StorageConfig } from '../storage-provider.interface'
4
+ import { logger } from '@things-factory/env'
5
+
6
+ export class FtpStorageProvider implements StorageProvider {
7
+ private client: ftp.Client
8
+ private config: StorageConfig
9
+ private connected: boolean = false
10
+
11
+ constructor(config: StorageConfig) {
12
+ this.config = config
13
+ this.client = new ftp.Client()
14
+ this.client.ftp.verbose = false
15
+ }
16
+
17
+ async connect(): Promise<void> {
18
+ try {
19
+ await this.client.access({
20
+ host: this.config.host!,
21
+ port: this.config.port || 21,
22
+ user: this.config.username!,
23
+ password: this.config.password!,
24
+ secure: false // Set to true for FTPS
25
+ })
26
+
27
+ this.connected = true
28
+ logger.info('FTP Storage Provider connected successfully')
29
+ } catch (error) {
30
+ this.connected = false
31
+ throw new Error(`Failed to connect to FTP: ${error}`)
32
+ }
33
+ }
34
+
35
+ async disconnect(): Promise<void> {
36
+ if (this.connected) {
37
+ this.client.close()
38
+ this.connected = false
39
+ logger.info('FTP Storage Provider disconnected')
40
+ }
41
+ }
42
+
43
+ isConnected(): boolean {
44
+ return this.connected
45
+ }
46
+
47
+ async readFile(path: string, encoding?: string): Promise<any> {
48
+ const fullPath = this.getFullPath(path)
49
+
50
+ // Create a temporary file to download to
51
+ const tempFile = `/tmp/temp_${Date.now()}.tmp`
52
+
53
+ try {
54
+ await this.client.downloadTo(tempFile, fullPath)
55
+ const fs = require('fs')
56
+ const content = fs.readFileSync(tempFile)
57
+ fs.unlinkSync(tempFile) // Clean up temp file
58
+
59
+ if (encoding) {
60
+ return content.toString(encoding as any)
61
+ }
62
+
63
+ return content
64
+ } catch (error) {
65
+ // Clean up temp file if it exists
66
+ const fs = require('fs')
67
+ if (fs.existsSync(tempFile)) {
68
+ fs.unlinkSync(tempFile)
69
+ }
70
+ throw error
71
+ }
72
+ }
73
+
74
+ async writeFile(path: string, content: any, encoding?: string): Promise<void> {
75
+ const fullPath = this.getFullPath(path)
76
+
77
+ if (typeof content === 'string') {
78
+ await this.client.uploadFrom(content, fullPath)
79
+ } else {
80
+ // Convert to string if it's a buffer or other type
81
+ const contentString = content.toString(encoding || 'utf8')
82
+ await this.client.uploadFrom(contentString, fullPath)
83
+ }
84
+ }
85
+
86
+ async uploadFile(params: {
87
+ stream: any
88
+ filename: string
89
+ uploadPath: string
90
+ }): Promise<{ id: string; path: string; size: number }> {
91
+ const fullPath = this.getFullPath(`${params.uploadPath}/${params.filename}`)
92
+ let size = 0
93
+
94
+ return new Promise<{ id: string; path: string; size: number }>((resolve, reject) => {
95
+ params.stream
96
+ .on('data', (chunk: any) => (size += chunk.length))
97
+ .on('error', (error: any) => reject(error))
98
+ .on('end', async () => {
99
+ try {
100
+ await this.client.uploadFrom(params.stream, fullPath)
101
+ resolve({
102
+ id: Date.now().toString(),
103
+ path: params.uploadPath,
104
+ size
105
+ })
106
+ } catch (error) {
107
+ reject(error)
108
+ }
109
+ })
110
+ })
111
+ }
112
+
113
+ async deleteFile(path: string): Promise<void> {
114
+ const fullPath = this.getFullPath(path)
115
+ await this.client.remove(fullPath)
116
+ }
117
+
118
+ async moveFile(params: { source: string; destination: string }): Promise<void> {
119
+ const sourcePath = this.getFullPath(params.source)
120
+ const destPath = this.getFullPath(params.destination)
121
+
122
+ // FTP doesn't have native move, so we copy then delete
123
+ const tempFile = `/tmp/temp_move_${Date.now()}.tmp`
124
+
125
+ try {
126
+ await this.client.downloadTo(tempFile, sourcePath)
127
+ await this.client.uploadFrom(tempFile, destPath)
128
+ await this.client.remove(sourcePath)
129
+ } finally {
130
+ // Clean up temp file
131
+ const fs = require('fs')
132
+ if (fs.existsSync(tempFile)) {
133
+ fs.unlinkSync(tempFile)
134
+ }
135
+ }
136
+ }
137
+
138
+ async readFolders(params: { path: string }): Promise<any[]> {
139
+ const fullPath = this.getFullPath(params.path)
140
+ const list = await this.client.list(fullPath)
141
+
142
+ // Filter for files only, checking if it's not a directory
143
+ return list.filter(item => item.type !== FileType.Directory)
144
+ }
145
+
146
+ async createDirectory(path: string): Promise<void> {
147
+ const fullPath = this.getFullPath(path)
148
+ await this.client.ensureDir(fullPath)
149
+ }
150
+
151
+ async deleteDirectory(path: string): Promise<void> {
152
+ const fullPath = this.getFullPath(path)
153
+ await this.client.removeDir(fullPath)
154
+ }
155
+
156
+ async fileExists(path: string): Promise<boolean> {
157
+ try {
158
+ const fullPath = this.getFullPath(path)
159
+ await this.client.size(fullPath)
160
+ return true
161
+ } catch (error) {
162
+ return false
163
+ }
164
+ }
165
+
166
+ async getFileSize(path: string): Promise<number> {
167
+ const fullPath = this.getFullPath(path)
168
+ return await this.client.size(fullPath)
169
+ }
170
+
171
+ private getFullPath(path: string): string {
172
+ if (this.config.basePath) {
173
+ return `${this.config.basePath}/${path}`.replace(/\/+/g, '/')
174
+ }
175
+ return path
176
+ }
177
+ }
@@ -0,0 +1,159 @@
1
+ import * as fs from 'fs'
2
+ import * as path from 'path'
3
+ import { StorageProvider, StorageConfig } from '../storage-provider.interface'
4
+ import { logger } from '@things-factory/env'
5
+
6
+ export class LocalStorageProvider implements StorageProvider {
7
+ private config: StorageConfig
8
+ private connected: boolean = false
9
+
10
+ constructor(config: StorageConfig) {
11
+ this.config = config
12
+ }
13
+
14
+ async connect(): Promise<void> {
15
+ try {
16
+ const basePath = this.config.basePath || './storage'
17
+
18
+ // Ensure base directory exists
19
+ if (!fs.existsSync(basePath)) {
20
+ fs.mkdirSync(basePath, { recursive: true })
21
+ }
22
+
23
+ this.connected = true
24
+ logger.info('Local Storage Provider connected successfully')
25
+ } catch (error) {
26
+ this.connected = false
27
+ throw new Error(`Failed to connect to Local Storage: ${error}`)
28
+ }
29
+ }
30
+
31
+ async disconnect(): Promise<void> {
32
+ this.connected = false
33
+ logger.info('Local Storage Provider disconnected')
34
+ }
35
+
36
+ isConnected(): boolean {
37
+ return this.connected
38
+ }
39
+
40
+ async readFile(filePath: string, encoding?: string): Promise<any> {
41
+ const fullPath = this.getFullPath(filePath)
42
+
43
+ if (encoding) {
44
+ return fs.readFileSync(fullPath, encoding as BufferEncoding)
45
+ }
46
+
47
+ return fs.readFileSync(fullPath)
48
+ }
49
+
50
+ async writeFile(filePath: string, content: any, encoding?: string): Promise<void> {
51
+ const fullPath = this.getFullPath(filePath)
52
+
53
+ // Ensure directory exists
54
+ const dir = path.dirname(fullPath)
55
+ if (!fs.existsSync(dir)) {
56
+ fs.mkdirSync(dir, { recursive: true })
57
+ }
58
+
59
+ if (encoding) {
60
+ fs.writeFileSync(fullPath, content, encoding as BufferEncoding)
61
+ } else {
62
+ fs.writeFileSync(fullPath, content)
63
+ }
64
+ }
65
+
66
+ async uploadFile(params: {
67
+ stream: any
68
+ filename: string
69
+ uploadPath: string
70
+ }): Promise<{ id: string; path: string; size: number }> {
71
+ const fullPath = this.getFullPath(`${params.uploadPath}/${params.filename}`)
72
+
73
+ // Ensure directory exists
74
+ const dir = path.dirname(fullPath)
75
+ if (!fs.existsSync(dir)) {
76
+ fs.mkdirSync(dir, { recursive: true })
77
+ }
78
+
79
+ let size = 0
80
+
81
+ return new Promise<{ id: string; path: string; size: number }>((resolve, reject) => {
82
+ const writeStream = fs.createWriteStream(fullPath)
83
+
84
+ params.stream
85
+ .pipe(writeStream)
86
+ .on('error', (error: any) => reject(error))
87
+ .on('data', (chunk: any) => (size += chunk.length))
88
+ .on('finish', () => {
89
+ resolve({
90
+ id: Date.now().toString(),
91
+ path: params.uploadPath,
92
+ size
93
+ })
94
+ })
95
+ })
96
+ }
97
+
98
+ async deleteFile(filePath: string): Promise<void> {
99
+ const fullPath = this.getFullPath(filePath)
100
+ fs.unlinkSync(fullPath)
101
+ }
102
+
103
+ async moveFile(params: { source: string; destination: string }): Promise<void> {
104
+ const sourcePath = this.getFullPath(params.source)
105
+ const destPath = this.getFullPath(params.destination)
106
+
107
+ // Ensure destination directory exists
108
+ const dir = path.dirname(destPath)
109
+ if (!fs.existsSync(dir)) {
110
+ fs.mkdirSync(dir, { recursive: true })
111
+ }
112
+
113
+ fs.renameSync(sourcePath, destPath)
114
+ }
115
+
116
+ async readFolders(params: { path: string }): Promise<any[]> {
117
+ const fullPath = this.getFullPath(params.path)
118
+
119
+ if (!fs.existsSync(fullPath)) {
120
+ return []
121
+ }
122
+
123
+ const items = fs.readdirSync(fullPath, { withFileTypes: true })
124
+ return items
125
+ .filter(item => item.isFile())
126
+ .map(item => ({
127
+ Key: path.join(params.path, item.name),
128
+ Size: fs.statSync(path.join(fullPath, item.name)).size
129
+ }))
130
+ }
131
+
132
+ async createDirectory(dirPath: string): Promise<void> {
133
+ const fullPath = this.getFullPath(dirPath)
134
+ fs.mkdirSync(fullPath, { recursive: true })
135
+ }
136
+
137
+ async deleteDirectory(dirPath: string): Promise<void> {
138
+ const fullPath = this.getFullPath(dirPath)
139
+ fs.rmSync(fullPath, { recursive: true, force: true })
140
+ }
141
+
142
+ async fileExists(filePath: string): Promise<boolean> {
143
+ const fullPath = this.getFullPath(filePath)
144
+ return fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()
145
+ }
146
+
147
+ async getFileSize(filePath: string): Promise<number> {
148
+ const fullPath = this.getFullPath(filePath)
149
+ const stats = fs.statSync(fullPath)
150
+ return stats.size
151
+ }
152
+
153
+ private getFullPath(filePath: string): string {
154
+ if (this.config.basePath) {
155
+ return path.resolve(this.config.basePath, filePath)
156
+ }
157
+ return path.resolve(filePath)
158
+ }
159
+ }
@@ -0,0 +1,214 @@
1
+ import AWS from 'aws-sdk'
2
+ import { v4 as uuidv4 } from 'uuid'
3
+ import { StorageProvider, StorageConfig } from '../storage-provider.interface'
4
+ import { logger } from '@things-factory/env'
5
+
6
+ const { PassThrough } = require('stream')
7
+
8
+ export class S3StorageProvider implements StorageProvider {
9
+ private s3: AWS.S3
10
+ private config: StorageConfig
11
+ private connected: boolean = false
12
+
13
+ constructor(config: StorageConfig) {
14
+ this.config = config
15
+ this.s3 = new AWS.S3({
16
+ accessKeyId: config.accessKeyId,
17
+ secretAccessKey: config.secretAccessKey,
18
+ region: config.region
19
+ })
20
+ }
21
+
22
+ async connect(): Promise<void> {
23
+ try {
24
+ // Assume client ready; defer validation to first real operation (write/read)
25
+ this.connected = true
26
+ logger.info('S3 Storage Provider ready')
27
+ } catch (error) {
28
+ this.connected = false
29
+ throw new Error(`Failed to initialize S3 client: ${error}`)
30
+ }
31
+ }
32
+
33
+ async disconnect(): Promise<void> {
34
+ this.connected = false
35
+ logger.info('S3 Storage Provider disconnected')
36
+ }
37
+
38
+ isConnected(): boolean {
39
+ return this.connected
40
+ }
41
+
42
+ async readFile(path: string, encoding?: string): Promise<any> {
43
+ const result = await this.s3
44
+ .getObject({
45
+ Bucket: this.config.bucketName!,
46
+ Key: path
47
+ })
48
+ .promise()
49
+
50
+ const body = result.Body
51
+
52
+ if (encoding) {
53
+ return body.toString(encoding as BufferEncoding)
54
+ }
55
+
56
+ return body
57
+ }
58
+
59
+ async writeFile(path: string, content: any, encoding?: string): Promise<void> {
60
+ let body: Buffer
61
+
62
+ if (typeof content === 'string' && encoding) {
63
+ body = Buffer.from(content, encoding as any)
64
+ } else {
65
+ body = Buffer.from(content)
66
+ }
67
+
68
+ const putParams: AWS.S3.PutObjectRequest = {
69
+ Bucket: this.config.bucketName!,
70
+ Key: path,
71
+ Body: body
72
+ }
73
+
74
+ // Optional SSE if configured via env/config
75
+ const sse = (this.config as any).serverSideEncryption
76
+ const kmsKeyId = (this.config as any).sseKmsKeyId
77
+ if (sse) {
78
+ putParams.ServerSideEncryption = sse
79
+ if (sse === 'aws:kms' && kmsKeyId) {
80
+ putParams.SSEKMSKeyId = kmsKeyId
81
+ }
82
+ }
83
+
84
+ await this.s3.putObject(putParams).promise()
85
+ }
86
+
87
+ async uploadFile(params: {
88
+ stream: any
89
+ filename: string
90
+ uploadPath: string
91
+ }): Promise<{ id: string; path: string; size: number }> {
92
+ const id = uuidv4()
93
+ let size: number = 0
94
+
95
+ return new Promise<{ id: string; path: string; size: number }>((resolve, reject) =>
96
+ params.stream
97
+ .pipe(
98
+ (() => {
99
+ var pass = new PassThrough()
100
+
101
+ this.s3.upload(
102
+ {
103
+ Bucket: this.config.bucketName!,
104
+ Key: params.uploadPath + '/' + params.filename,
105
+ Body: pass,
106
+ ServerSideEncryption: (this.config as any).serverSideEncryption,
107
+ SSEKMSKeyId: (this.config as any).sseKmsKeyId
108
+ },
109
+ (err, data) => (err ? reject(err) : resolve({ id, path: params.uploadPath, size }))
110
+ )
111
+
112
+ return pass
113
+ })()
114
+ )
115
+ .on('error', error => reject(error))
116
+ .on('data', chunk => (size += chunk.length))
117
+ )
118
+ }
119
+
120
+ async deleteFile(path: string): Promise<void> {
121
+ await this.s3
122
+ .deleteObject({
123
+ Bucket: this.config.bucketName!,
124
+ Key: path
125
+ })
126
+ .promise()
127
+ }
128
+
129
+ async moveFile(params: { source: string; destination: string }): Promise<void> {
130
+ try {
131
+ // Copy file to new location
132
+ await this.s3
133
+ .copyObject({
134
+ Bucket: this.config.bucketName!,
135
+ CopySource: this.config.bucketName + '/' + params.source,
136
+ Key: params.destination
137
+ })
138
+ .promise()
139
+
140
+ // Delete original file
141
+ await this.deleteFile(params.source)
142
+ } catch (error) {
143
+ throw new Error(`Failed to move file: ${error}`)
144
+ }
145
+ }
146
+
147
+ async readFolders(params: { path: string }): Promise<any[]> {
148
+ const s3Params = {
149
+ Bucket: this.config.bucketName!,
150
+ Delimiter: '/',
151
+ Prefix: params.path
152
+ }
153
+
154
+ const result = await this.s3.listObjects(s3Params).promise()
155
+ return result.Contents || []
156
+ }
157
+
158
+ async createDirectory(path: string): Promise<void> {
159
+ // S3 doesn't have real directories, but we can create an empty object
160
+ await this.s3
161
+ .putObject({
162
+ Bucket: this.config.bucketName!,
163
+ Key: path.endsWith('/') ? path : path + '/',
164
+ Body: ''
165
+ })
166
+ .promise()
167
+ }
168
+
169
+ async deleteDirectory(path: string): Promise<void> {
170
+ // List all objects with the prefix and delete them
171
+ const objects = await this.s3
172
+ .listObjects({
173
+ Bucket: this.config.bucketName!,
174
+ Prefix: path
175
+ })
176
+ .promise()
177
+
178
+ if (objects.Contents && objects.Contents.length > 0) {
179
+ const deleteParams = {
180
+ Bucket: this.config.bucketName!,
181
+ Delete: {
182
+ Objects: objects.Contents.map(obj => ({ Key: obj.Key! }))
183
+ }
184
+ }
185
+
186
+ await this.s3.deleteObjects(deleteParams).promise()
187
+ }
188
+ }
189
+
190
+ async fileExists(path: string): Promise<boolean> {
191
+ try {
192
+ await this.s3
193
+ .headObject({
194
+ Bucket: this.config.bucketName!,
195
+ Key: path
196
+ })
197
+ .promise()
198
+ return true
199
+ } catch (error) {
200
+ return false
201
+ }
202
+ }
203
+
204
+ async getFileSize(path: string): Promise<number> {
205
+ const result = await this.s3
206
+ .headObject({
207
+ Bucket: this.config.bucketName!,
208
+ Key: path
209
+ })
210
+ .promise()
211
+
212
+ return result.ContentLength || 0
213
+ }
214
+ }