@defra-fish/pocl-job 1.55.0 → 1.56.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra-fish/pocl-job",
3
- "version": "1.55.0",
3
+ "version": "1.56.0",
4
4
  "description": "Post Office Counter Licence sales processor",
5
5
  "type": "module",
6
6
  "engines": {
@@ -35,16 +35,15 @@
35
35
  "test": "echo \"Error: run tests from root\" && exit 1"
36
36
  },
37
37
  "dependencies": {
38
- "@defra-fish/business-rules-lib": "1.55.0",
39
- "@defra-fish/connectors-lib": "1.55.0",
38
+ "@defra-fish/business-rules-lib": "1.56.0",
39
+ "@defra-fish/connectors-lib": "1.56.0",
40
40
  "commander": "^7.2.0",
41
41
  "debug": "^4.3.3",
42
42
  "filesize": "^6.4.0",
43
43
  "md5-file": "^5.0.0",
44
44
  "moment": "^2.29.1",
45
45
  "moment-timezone": "^0.5.34",
46
- "sax-stream": "^1.3.0",
47
- "ssh2-sftp-client": "^6.0.1"
46
+ "sax-stream": "^1.3.0"
48
47
  },
49
- "gitHead": "3405322b81a2016ba22e530de8d096a49be0bea4"
48
+ "gitHead": "4edcdd349b077ad0bdb3e7b4029df3aba7c04b49"
50
49
  }
@@ -11,11 +11,6 @@ describe('config', () => {
11
11
  process.env.POCL_RECORD_STAGING_TABLE = 'test-record-staging-table'
12
12
  process.env.POCL_STAGING_TTL = 1234
13
13
 
14
- process.env.POCL_FTP_HOST = 'test-host'
15
- process.env.POCL_FTP_PORT = 2222
16
- process.env.POCL_FTP_PATH = '/remote/share'
17
- process.env.POCL_FTP_USERNAME = 'test-user'
18
- process.env.POCL_FTP_KEY_SECRET_ID = 'test-secret-id'
19
14
  process.env.POCL_S3_BUCKET = 'test-bucket'
20
15
  await config.initialise()
21
16
  })
@@ -38,32 +33,6 @@ describe('config', () => {
38
33
  })
39
34
  })
40
35
 
41
- describe('ftp', () => {
42
- it('provides properties relating the use of SFTP', async () => {
43
- expect(config.ftp).toEqual(
44
- expect.objectContaining({
45
- host: 'test-host',
46
- port: '2222',
47
- path: '/remote/share',
48
- username: 'test-user',
49
- privateKey: 'test-ssh-key',
50
- algorithms: { cipher: expect.any(Array), kex: expect.any(Array) },
51
- // Wait up to 60 seconds for the SSH handshake
52
- readyTimeout: expect.any(Number),
53
- // Retry 5 times over a minute
54
- retries: expect.any(Number),
55
- retry_minTimeout: expect.any(Number),
56
- debug: expect.any(Function)
57
- })
58
- )
59
- })
60
- it('defaults the sftp port to 22 if the environment variable is not configured', async () => {
61
- delete process.env.POCL_FTP_PORT
62
- await config.initialise()
63
- expect(config.ftp.port).toEqual('22')
64
- })
65
- })
66
-
67
36
  describe('s3', () => {
68
37
  it('provides properties relating the use of Amazon S3', async () => {
69
38
  expect(config.s3.bucket).toEqual('test-bucket')
@@ -41,7 +41,7 @@ jest.mock('../config.js', () => ({
41
41
  bucket: 'testbucket'
42
42
  }
43
43
  }))
44
- jest.mock('../transport/ftp-to-s3.js')
44
+ jest.mock('../transport/storeS3MetaData.js')
45
45
  jest.mock('../transport/s3-to-local.js')
46
46
  jest.mock('../io/db.js')
47
47
  jest.mock('../io/s3.js')
package/src/config.js CHANGED
@@ -1,49 +1,5 @@
1
- import { AWS } from '@defra-fish/connectors-lib'
2
- import db from 'debug'
3
- const { secretsManager } = AWS()
4
-
5
- /**
6
- * Key exchange algorithms for public key authentication - in descending order of priority
7
- * @type {string[]}
8
- */
9
- export const SFTP_KEY_EXCHANGE_ALGORITHMS = [
10
- 'curve25519-sha256@libssh.org',
11
- 'curve25519-sha256',
12
- 'ecdh-sha2-nistp521',
13
- 'ecdh-sha2-nistp384',
14
- 'ecdh-sha2-nistp256',
15
- 'diffie-hellman-group-exchange-sha256',
16
- 'diffie-hellman-group14-sha256',
17
- 'diffie-hellman-group16-sha512',
18
- 'diffie-hellman-group18-sha512',
19
- 'diffie-hellman-group14-sha1',
20
- 'diffie-hellman-group-exchange-sha1',
21
- 'diffie-hellman-group1-sha1'
22
- ]
23
- /**
24
- * Ciphers for SFTP support - in descending order of priority
25
- * @type {string[]}
26
- */
27
- export const SFTP_CIPHERS = [
28
- // http://tools.ietf.org/html/rfc4344#section-4
29
- 'aes256-ctr',
30
- 'aes192-ctr',
31
- 'aes128-ctr',
32
- 'aes256-gcm',
33
- 'aes256-gcm@openssh.com',
34
- 'aes128-gcm',
35
- 'aes128-gcm@openssh.com',
36
- 'aes256-cbc',
37
- 'aes192-cbc',
38
- 'aes128-cbc',
39
- 'blowfish-cbc',
40
- '3des-cbc',
41
- 'cast128-cbc'
42
- ]
43
-
44
1
  class Config {
45
2
  _db
46
- _ftp
47
3
  _s3
48
4
 
49
5
  async initialise () {
@@ -53,21 +9,6 @@ class Config {
53
9
  stagingTtlDelta: Number.parseInt(process.env.POCL_STAGING_TTL || 60 * 60 * 168)
54
10
  }
55
11
 
56
- this.ftp = {
57
- host: process.env.POCL_FTP_HOST,
58
- port: process.env.POCL_FTP_PORT || '22',
59
- path: process.env.POCL_FTP_PATH,
60
- username: process.env.POCL_FTP_USERNAME,
61
- privateKey: (await secretsManager.getSecretValue({ SecretId: process.env.POCL_FTP_KEY_SECRET_ID }).promise()).SecretString,
62
- algorithms: { cipher: SFTP_CIPHERS, kex: SFTP_KEY_EXCHANGE_ALGORITHMS },
63
- // Wait up to 60 seconds for the SSH handshake
64
- readyTimeout: 60000,
65
- // Retry 5 times over a minute
66
- retries: 5,
67
- retry_minTimeout: 12000,
68
- debug: db('pocl:ftp')
69
- }
70
-
71
12
  this.s3 = {
72
13
  bucket: process.env.POCL_S3_BUCKET
73
14
  }
@@ -85,18 +26,6 @@ class Config {
85
26
  this._db = cfg
86
27
  }
87
28
 
88
- /**
89
- * FTP configuration settings
90
- * @type {object}
91
- */
92
- get ftp () {
93
- return this._ftp
94
- }
95
-
96
- set ftp (cfg) {
97
- this._ftp = cfg
98
- }
99
-
100
29
  /**
101
30
  * S3 configuration settings
102
31
  * @type {object}
@@ -5,7 +5,6 @@ import { DYNAMICS_IMPORT_STAGE, FILE_STAGE, POST_OFFICE_DATASOURCE } from '../..
5
5
  import { salesApi } from '@defra-fish/connectors-lib'
6
6
  import fs from 'fs'
7
7
  import AwsMock from 'aws-sdk'
8
- import { mockedFtpMethods } from 'ssh2-sftp-client'
9
8
 
10
9
  jest.mock('fs')
11
10
  jest.mock('md5-file')
@@ -159,7 +158,6 @@ describe('s3 operations', () => {
159
158
  })
160
159
 
161
160
  it('skips file processing if a file has already been marked as processed in Dynamics', async () => {
162
- mockedFtpMethods.list.mockResolvedValue([{ name: 'test-already-processed.xml' }])
163
161
  fs.createReadStream.mockReturnValueOnce('teststream')
164
162
  fs.statSync.mockReturnValueOnce({ size: 1024 })
165
163
  salesApi.getTransactionFile.mockResolvedValueOnce({ status: { description: 'Processed' } })
package/src/io/s3.js CHANGED
@@ -2,7 +2,7 @@ import moment from 'moment'
2
2
  import filesize from 'filesize'
3
3
  import config from '../config.js'
4
4
  import { DYNAMICS_IMPORT_STAGE } from '../staging/constants.js'
5
- import { storeS3Metadata } from '../transport/ftp-to-s3.js'
5
+ import { storeS3Metadata } from '../transport/storeS3MetaData.js'
6
6
  import { AWS, salesApi } from '@defra-fish/connectors-lib'
7
7
  const { s3 } = AWS()
8
8
 
@@ -0,0 +1,64 @@
1
+ import moment from 'moment'
2
+ import { salesApi } from '@defra-fish/connectors-lib'
3
+ import { DYNAMICS_IMPORT_STAGE, FILE_STAGE, POST_OFFICE_DATASOURCE } from '../../staging/constants.js'
4
+ import { updateFileStagingTable } from '../../io/db.js'
5
+ import { storeS3Metadata } from '../storeS3MetaData.js'
6
+
7
+ jest.mock('../../io/db.js', () => ({
8
+ updateFileStagingTable: jest.fn()
9
+ }))
10
+ jest.mock('@defra-fish/connectors-lib', () => ({
11
+ salesApi: {
12
+ upsertTransactionFile: jest.fn()
13
+ }
14
+ }))
15
+
16
+ describe('storeS3Metadata', () => {
17
+ const md5 = 'mockMd5Hash'
18
+ const fileSize = 12345
19
+ const filename = 'testfile'
20
+ const s3Key = 'mock/s3/key/testfile'
21
+ const receiptMoment = new Date('2024-10-17T00:00:00Z')
22
+
23
+ beforeEach(() => {
24
+ jest.clearAllMocks()
25
+ })
26
+
27
+ it('console log should output "Storing metadata for s3Key"', async () => {
28
+ const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(jest.fn())
29
+ await storeS3Metadata(md5, fileSize, filename, s3Key, receiptMoment)
30
+ expect(consoleLogSpy).toHaveBeenCalledWith(`Storing metadata for ${s3Key}`)
31
+ })
32
+
33
+ it('should call updateFileStagingTable with correct arguments', async () => {
34
+ await storeS3Metadata(md5, fileSize, filename, s3Key, receiptMoment)
35
+ expect(updateFileStagingTable).toHaveBeenCalledWith({
36
+ filename,
37
+ md5,
38
+ fileSize,
39
+ s3Key,
40
+ stage: FILE_STAGE.Pending
41
+ })
42
+ })
43
+
44
+ it('should call salesApi.upsertTransactionFile with correct arguments', async () => {
45
+ const expectedSalesDate = moment(receiptMoment).subtract(1, 'days').toISOString()
46
+ const expectedReceiptTimestamp = receiptMoment.toISOString()
47
+
48
+ await storeS3Metadata(md5, fileSize, filename, s3Key, receiptMoment)
49
+ expect(salesApi.upsertTransactionFile).toHaveBeenCalledWith(filename, {
50
+ status: DYNAMICS_IMPORT_STAGE.Pending,
51
+ dataSource: POST_OFFICE_DATASOURCE,
52
+ fileSize: fileSize,
53
+ salesDate: expectedSalesDate,
54
+ receiptTimestamp: expectedReceiptTimestamp,
55
+ notes: 'Retrieved from the remote server and awaiting processing'
56
+ })
57
+ })
58
+
59
+ test('should log "Stored metadata for s3Key"', async () => {
60
+ const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(jest.fn())
61
+ await storeS3Metadata(md5, fileSize, filename, s3Key, receiptMoment)
62
+ expect(consoleLogSpy).toHaveBeenCalledWith(`Stored metadata for ${s3Key}`)
63
+ })
64
+ })
@@ -0,0 +1,20 @@
1
+ import moment from 'moment'
2
+ import { salesApi } from '@defra-fish/connectors-lib'
3
+ import { DYNAMICS_IMPORT_STAGE, FILE_STAGE, POST_OFFICE_DATASOURCE } from '../staging/constants.js'
4
+ import { updateFileStagingTable } from '../io/db.js'
5
+
6
+ export async function storeS3Metadata (md5, fileSize, filename, s3Key, receiptMoment) {
7
+ console.log(`Storing metadata for ${s3Key}`)
8
+ await updateFileStagingTable({ filename, md5, fileSize, s3Key, stage: FILE_STAGE.Pending })
9
+
10
+ await salesApi.upsertTransactionFile(filename, {
11
+ status: DYNAMICS_IMPORT_STAGE.Pending,
12
+ dataSource: POST_OFFICE_DATASOURCE,
13
+ fileSize: fileSize,
14
+ salesDate: moment(receiptMoment).subtract(1, 'days').toISOString(),
15
+ receiptTimestamp: receiptMoment.toISOString(),
16
+ notes: 'Retrieved from the remote server and awaiting processing'
17
+ })
18
+
19
+ console.log(`Stored metadata for ${s3Key}`)
20
+ }
@@ -1,11 +0,0 @@
1
- const ssh2sftpClient = jest.genMockFromModule('ssh2-sftp-client')
2
-
3
- export const mockedFtpMethods = {
4
- connect: jest.fn(),
5
- list: jest.fn(),
6
- fastGet: jest.fn(),
7
- delete: jest.fn(),
8
- end: jest.fn()
9
- }
10
- ssh2sftpClient.mockImplementation(() => mockedFtpMethods)
11
- export default ssh2sftpClient
@@ -1,145 +0,0 @@
1
- import { ftpToS3 } from '../ftp-to-s3.js'
2
- import moment from 'moment'
3
- import { updateFileStagingTable } from '../../io/db.js'
4
- import { getTempDir } from '../../io/file.js'
5
- import { DYNAMICS_IMPORT_STAGE, FILE_STAGE, POST_OFFICE_DATASOURCE } from '../../staging/constants.js'
6
- import { salesApi } from '@defra-fish/connectors-lib'
7
- import fs from 'fs'
8
- import md5File from 'md5-file'
9
- import AwsMock from 'aws-sdk'
10
- import { mockedFtpMethods } from 'ssh2-sftp-client'
11
-
12
- jest.mock('fs')
13
- jest.mock('md5-file')
14
- jest.mock('../../io/db.js')
15
- jest.mock('../../io/file.js')
16
-
17
- jest.mock('@defra-fish/connectors-lib', () => {
18
- const actual = jest.requireActual('@defra-fish/connectors-lib')
19
- return {
20
- AWS: actual.AWS,
21
- salesApi: {
22
- ...Object.keys(actual.salesApi).reduce((acc, k) => ({ ...acc, [k]: jest.fn(async () => {}) }), {})
23
- }
24
- }
25
- })
26
-
27
- jest.mock('../../config.js', () => ({
28
- ftp: {
29
- path: '/ftpservershare/'
30
- },
31
- s3: {
32
- bucket: 'testbucket'
33
- }
34
- }))
35
-
36
- describe('ftp-to-s3', () => {
37
- beforeAll(() => {
38
- getTempDir.mockReturnValue('/local/tmp')
39
- md5File.mockResolvedValue('example-md5')
40
- })
41
- beforeEach(() => {
42
- jest.clearAllMocks()
43
- AwsMock.__resetAll()
44
- })
45
-
46
- it('retrieves files from SFTP and stores in S3', async () => {
47
- mockedFtpMethods.list.mockResolvedValue([{ name: 'test1.xml' }, { name: 'test2.xml' }])
48
- fs.createReadStream.mockReturnValueOnce('test1stream')
49
- fs.createReadStream.mockReturnValueOnce('test2stream')
50
- fs.statSync.mockReturnValueOnce({ size: 1024 })
51
- fs.statSync.mockReturnValueOnce({ size: 2048 })
52
- await ftpToS3()
53
-
54
- const localPath1 = '/local/tmp/test1.xml'
55
- const localPath2 = '/local/tmp/test2.xml'
56
-
57
- const s3Key1 = `${moment().format('YYYY-MM-DD')}/test1.xml`
58
- const s3Key2 = `${moment().format('YYYY-MM-DD')}/test2.xml`
59
-
60
- expect(mockedFtpMethods.fastGet).toHaveBeenNthCalledWith(1, '/ftpservershare/test1.xml', localPath1, {})
61
- expect(mockedFtpMethods.fastGet).toHaveBeenNthCalledWith(2, '/ftpservershare/test2.xml', localPath2, {})
62
- expect(AwsMock.S3.mockedMethods.putObject).toHaveBeenNthCalledWith(1, {
63
- Bucket: 'testbucket',
64
- Key: s3Key1,
65
- Body: 'test1stream'
66
- })
67
- expect(AwsMock.S3.mockedMethods.putObject).toHaveBeenNthCalledWith(2, {
68
- Bucket: 'testbucket',
69
- Key: s3Key2,
70
- Body: 'test2stream'
71
- })
72
- expect(updateFileStagingTable).toHaveBeenNthCalledWith(1, {
73
- filename: 'test1.xml',
74
- md5: 'example-md5',
75
- fileSize: '1 KB',
76
- stage: FILE_STAGE.Pending,
77
- s3Key: s3Key1
78
- })
79
- expect(updateFileStagingTable).toHaveBeenNthCalledWith(2, {
80
- filename: 'test2.xml',
81
- md5: 'example-md5',
82
- fileSize: '2 KB',
83
- stage: FILE_STAGE.Pending,
84
- s3Key: s3Key2
85
- })
86
- expect(salesApi.upsertTransactionFile).toHaveBeenNthCalledWith(1, 'test1.xml', {
87
- status: DYNAMICS_IMPORT_STAGE.Pending,
88
- dataSource: POST_OFFICE_DATASOURCE,
89
- fileSize: '1 KB',
90
- receiptTimestamp: expect.any(String),
91
- salesDate: expect.any(String),
92
- notes: 'Retrieved from the remote server and awaiting processing'
93
- })
94
- expect(salesApi.upsertTransactionFile).toHaveBeenNthCalledWith(2, 'test2.xml', {
95
- status: DYNAMICS_IMPORT_STAGE.Pending,
96
- dataSource: POST_OFFICE_DATASOURCE,
97
- fileSize: '2 KB',
98
- receiptTimestamp: expect.any(String),
99
- salesDate: expect.any(String),
100
- notes: 'Retrieved from the remote server and awaiting processing'
101
- })
102
- expect(fs.unlinkSync).toHaveBeenNthCalledWith(1, localPath1)
103
- expect(fs.unlinkSync).toHaveBeenNthCalledWith(2, localPath2)
104
- expect(mockedFtpMethods.end).toHaveBeenCalledTimes(1)
105
- })
106
-
107
- it('moves the file to s3 but skips file processing if a file has already been marked as processed in Dynamics', async () => {
108
- mockedFtpMethods.list.mockResolvedValue([{ name: 'test-already-processed.xml' }])
109
- fs.createReadStream.mockReturnValueOnce('teststream')
110
- fs.statSync.mockReturnValueOnce({ size: 1024 })
111
- salesApi.getTransactionFile.mockResolvedValueOnce({ status: { description: 'Processed' } })
112
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(jest.fn())
113
- await ftpToS3()
114
- const localPath = '/local/tmp/test-already-processed.xml'
115
- const s3Key = `${moment().format('YYYY-MM-DD')}/test-already-processed.xml`
116
- expect(mockedFtpMethods.fastGet).toHaveBeenCalledWith('/ftpservershare/test-already-processed.xml', localPath, {})
117
- expect(AwsMock.S3.mockedMethods.putObject).toHaveBeenCalledWith({
118
- Bucket: 'testbucket',
119
- Key: s3Key,
120
- Body: 'teststream'
121
- })
122
- expect(updateFileStagingTable).not.toHaveBeenCalled()
123
- expect(salesApi.upsertTransactionFile).not.toHaveBeenCalled()
124
- expect(consoleErrorSpy).toHaveBeenCalled()
125
- expect(fs.unlinkSync).toHaveBeenCalledWith(localPath)
126
- expect(mockedFtpMethods.end).toHaveBeenCalledTimes(1)
127
- })
128
-
129
- it('logs and propogates errors back up the stack', async () => {
130
- const testError = new Error('Test error')
131
- mockedFtpMethods.list.mockRejectedValue(testError)
132
- const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
133
- await expect(ftpToS3).rejects.toThrow(testError)
134
- expect(consoleErrorSpy).toHaveBeenCalled()
135
- })
136
-
137
- it('ignores non-xml files', async () => {
138
- mockedFtpMethods.list.mockResolvedValue([{ name: 'test1.pdf' }, { name: 'test2.md' }])
139
- await ftpToS3()
140
- expect(mockedFtpMethods.fastGet).not.toHaveBeenCalled()
141
- expect(AwsMock.S3.mockedMethods.putObject).not.toHaveBeenCalled()
142
- expect(fs.unlinkSync).not.toHaveBeenCalled()
143
- expect(mockedFtpMethods.end).toHaveBeenCalledTimes(1)
144
- })
145
- })
@@ -1,91 +0,0 @@
1
- import FtpClient from 'ssh2-sftp-client'
2
- import moment from 'moment'
3
- import Path from 'path'
4
- import fs from 'fs'
5
- import db from 'debug'
6
- import md5File from 'md5-file'
7
- import filesize from 'filesize'
8
- import config from '../config.js'
9
- import { getTempDir } from '../io/file.js'
10
- import { DYNAMICS_IMPORT_STAGE, FILE_STAGE, POST_OFFICE_DATASOURCE } from '../staging/constants.js'
11
- import { AWS, salesApi } from '@defra-fish/connectors-lib'
12
- import { updateFileStagingTable } from '../io/db.js'
13
- const { s3 } = AWS()
14
-
15
- const debug = db('pocl:transport')
16
- const sftp = new FtpClient()
17
-
18
- export async function ftpToS3 () {
19
- try {
20
- debug('Connecting to SFTP endpoint at sftp://%s:%s%s', config.ftp.host, config.ftp.port, config.ftp.path)
21
- await sftp.connect(config.ftp)
22
- const fileList = await sftp.list(config.ftp.path)
23
- debug('Discovered the following files on the SFTP server: %o', fileList)
24
- const xmlFiles = fileList.filter(f => Path.extname(f.name).toLowerCase() === '.xml')
25
-
26
- if (!xmlFiles.length) {
27
- debug('No XML files were waiting to be processed on the SFTP server.')
28
- } else {
29
- await retrieveAllFiles(xmlFiles)
30
- }
31
- } catch (e) {
32
- console.error('Error migrating files from the SFTP endpoint', e)
33
- throw e
34
- } finally {
35
- debug('Closing SFTP connection.')
36
- await sftp.end()
37
- }
38
- }
39
-
40
- export async function storeS3Metadata (md5, fileSize, filename, s3Key, receiptMoment) {
41
- console.log(`Storing metadata for ${s3Key}`)
42
- await updateFileStagingTable({ filename, md5, fileSize, s3Key, stage: FILE_STAGE.Pending })
43
-
44
- await salesApi.upsertTransactionFile(filename, {
45
- status: DYNAMICS_IMPORT_STAGE.Pending,
46
- dataSource: POST_OFFICE_DATASOURCE,
47
- fileSize: fileSize,
48
- salesDate: moment(receiptMoment).subtract(1, 'days').toISOString(),
49
- receiptTimestamp: receiptMoment.toISOString(),
50
- notes: 'Retrieved from the remote server and awaiting processing'
51
- })
52
-
53
- console.log(`Stored metadata for ${s3Key}`)
54
- }
55
-
56
- const retrieveAllFiles = async xmlFiles => {
57
- const tempDir = getTempDir('ftp')
58
-
59
- for (const fileEntry of xmlFiles) {
60
- const filename = fileEntry.name
61
- const remoteFilePath = Path.join(config.ftp.path, filename)
62
- const localFilePath = Path.resolve(tempDir, filename)
63
-
64
- // Retrieve from FTP server to local temporary directory
65
- debug('Transferring %s to %s', remoteFilePath, localFilePath)
66
- await sftp.fastGet(remoteFilePath, localFilePath, {})
67
-
68
- // Transfer to S3
69
- const receiptMoment = moment()
70
- const s3Key = Path.join(receiptMoment.format('YYYY-MM-DD'), filename)
71
- debug('Transferring file to S3 bucket %s with key %s', config.s3.bucket, s3Key)
72
- await s3.putObject({ Bucket: config.s3.bucket, Key: s3Key, Body: fs.createReadStream(localFilePath) }).promise()
73
-
74
- const dynamicsRecord = await salesApi.getTransactionFile(filename)
75
- if (dynamicsRecord && DYNAMICS_IMPORT_STAGE.isAlreadyProcessed(dynamicsRecord.status.description)) {
76
- console.error(
77
- 'Retrieved file %s from SFTP and stored in S3, however an entry already exists in Dynamics with this filename. Skipping import.',
78
- filename
79
- )
80
- } else {
81
- const md5 = await md5File(localFilePath)
82
- const fileSize = filesize(fs.statSync(localFilePath).size)
83
- await storeS3Metadata(md5, fileSize, filename, s3Key, receiptMoment)
84
- }
85
-
86
- // Remove from FTP server and local tmp
87
- debug('Removing remote file %s', remoteFilePath)
88
- await sftp.delete(remoteFilePath)
89
- fs.unlinkSync(localFilePath)
90
- }
91
- }