This repository was archived by the owner on Jul 22, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 31
/
Copy pathsave-check-images.js
372 lines (343 loc) · 12.7 KB
/
save-check-images.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
/**
* Copyright 2016-2017 IBM Corp. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var openwhisk = require('openwhisk');
var request = require('request');
var async = require('async');
var fs = require('fs');
var uuid = require('node-uuid');
var gm = require('gm').subClass({
imageMagick: true
});
var Cloudant = require('cloudant');
/**
* This action is invoked when new check images are found in object storage.
* This action is idempotent. If it fails, it can be retried.
*
* 1. Retrieve the image from object storage
* 2. Resize the image into two additional copies at 50% and 25%
* 3. Store the resized images into an archive database for use by other applications
* 4. Store the original image into an audit database to initiate the OCR scan in another action
*
* @param params.CLOUDANT_USERNAME Cloudant username
* @param params.CLOUDANT_PASSWORD Cloudant password
* @param params.CLOUDANT_ARCHIVED_DATABASE Cloudant database to store the resized copies to
* @param params.CLOUDANT_AUDITED_DATABASE Cloudant database to store the original copy to
* @param params.OBJECT_STORAGE_USER_ID Object storage user id
* @param params.OBJECT_STORAGE_PASSWORD Object storage password
* @param params.OBJECT_STORAGE_PROJECT_ID Object storage project id
* @param params.OBJECT_STORAGE_REGION_NAME Object storage region
* @param params.OBJECT_STORAGE_INCOMING_CONTAINER_NAME Object storage container where the image is
* @return Standard OpenWhisk success/error response
*/
function main(params) {
// Configure database connection
var cloudant = new Cloudant({
account: params.CLOUDANT_USERNAME,
password: params.CLOUDANT_PASSWORD
});
var archivedDb = cloudant.db.use(params.CLOUDANT_ARCHIVED_DATABASE);
var auditedDb = cloudant.db.use(params.CLOUDANT_AUDITED_DATABASE);
// Configure object storage connection
var os = new ObjectStorage(
params.OBJECT_STORAGE_REGION_NAME,
params.OBJECT_STORAGE_PROJECT_ID,
params.OBJECT_STORAGE_USER_ID,
params.OBJECT_STORAGE_PASSWORD
);
// For the 50% and 25% scaled images and image type
var medFileName = "300px-" + params.fileName;
var smFileName = "150px-" + params.fileName;
var fileExtension = params.fileName.split('.').pop();
// This chains together the following functions serially, so that if there's an error along the way,
// the check isn't deleted and this can be called again idempotently.
return new Promise(function(resolve, reject) {
async.waterfall([
// Authenticate to object storage
function(callback) {
console.log("Authenticating...");
os.authenticate(function(err, response, body) {
return callback(err);
});
},
// Get the file on disk as a temp file
function(callback) {
console.log("Downloading", params.fileName);
os.downloadFile(params.OBJECT_STORAGE_INCOMING_CONTAINER_NAME, params.fileName, fs.createWriteStream(params.fileName), function(err) {
return callback(err);
});
},
// Copy and resize the file to two smaller versions
function(callback) {
console.log("Creating resized images.");
if (fileExtension == "bmp" || fileExtension == "jpg" || fileExtension == "png" || fileExtension == "gif") {
console.log("Resizing image to 300px wide");
gm(params.fileName).resize(300).write(medFileName, function(err) {
if (err) {
console.log("300px resize error: " + err);
return callback(err);
} else {
console.log("Resizing image to 150px wide");
gm(params.fileName).resize(150).write(smFileName, function(err) {
if (err) {
console.log("150px resize error: " + err);
return callback(err);
}
return callback(null);
});
}
});
} else {
return callback("File is not an image.");
}
},
// Open original file to memory and send it to the next function
function(callback) {
console.log("Opening original file");
fs.readFile(params.fileName, function(err, data) {
if (err) {
console.log("Error reading original file.");
return callback(err);
} else {
console.log("Success reading original file.");
return callback(null, data);
}
});
},
// Save original image data to Cloudant with an enriched name
function(data, callback) {
var uuid1 = uuid.v1();
var attachmentName = "att-" + uuid1;
console.log("Attempting insert of original image into the audited database. Id = " + uuid1);
var values = params.fileName.split('^');
var email = values[0];
var toAccount = values[1];
var amount = values[2];
auditedDb.multipart.insert({
fileName: params.fileName,
attachmentName: attachmentName,
email: email,
toAccount: toAccount,
amount: amount,
timestamp: (new Date()).getTime()
}, [{
name: attachmentName,
data: data,
content_type: params.contentType
}],
uuid1,
function(err, body) {
if (err && err.statusCode != 409) {
console.log("Error with original file insert.");
return callback(err);
} else {
console.log("Success with original file insert.");
return callback(null);
}
}
);
},
// Open medium file to memory and send it to the next function
function(callback) {
console.log("Opening medium file");
fs.readFile(medFileName, function(err, data) {
if (err) {
console.log("Error reading medium file.");
return callback(err);
} else {
console.log("Success reading medium file.");
return callback(null, data);
}
});
},
// Save medium file to Cloudant with an enriched name
function(data, callback) {
if (!data) return callback(null);
console.log("Attempting Cloudant insert of medium image into the archived database.");
var uuid1 = uuid.v1();
var attachmentName = uuid.v1(); //I'd rather use a simple md5 hash, but it's not available
archivedDb.multipart.insert({
fileName: medFileName,
attachmentName: attachmentName
}, [{
name: attachmentName,
data: data,
content_type: params.contentType
}],
uuid1,
function(err, body) {
if (err && err.statusCode != 409) {
console.log("Error with Cloudant medium insert.");
return callback(err);
} else {
console.log("Success with Cloudant medium file insert.");
return callback(null);
}
}
);
},
// Open small file to memory and send it to the next function
function(callback) {
console.log("Opening small file");
fs.readFile(smFileName, function(err, data) {
if (err) {
console.log("Error reading small file.");
return callback(err);
} else {
console.log("Success reading small file.");
return callback(null, data);
}
});
},
// Save small file to Cloudant with an enriched name
function(data, callback) {
if (!data) return callback(null);
console.log("Attempting Cloudant insert of small image into the archived database.");
var uuid1 = uuid.v1();
var attachmentName = uuid.v1(); //I'd rather use a simple md5 hash, but it's not available
archivedDb.multipart.insert({
fileName: smFileName,
attachmentName: attachmentName
}, [{
name: attachmentName,
data: data,
content_type: params.contentType
}],
uuid1,
function(err, body) {
if (err && err.statusCode != 409) {
console.log("Error with Cloudant small file insert.");
return callback(err);
} else {
console.log("Success with Cloudant small file insert.");
return callback(null);
}
}
);
},
// When all the steps above have completed successfully, delete the file from the incoming folder
function(callback) {
console.log("Deleting processed file from", params.OBJECT_STORAGE_INCOMING_CONTAINER_NAME);
os.deleteFile(params.OBJECT_STORAGE_INCOMING_CONTAINER_NAME, params.fileName, callback, function(err) {
if (err) {
return callback(err);
} else {
return callback(null);
}
});
}
],
function(err, result) {
if (err) {
console.log("Error", err);
reject(err);
} else {
resolve({
status: "Success"
});
}
}
);
});
}
/**
* This is an adapter class for OpenStack OBJECT_STORAGE based object storage.
*
* @param region The id of the record in the Cloudant 'processed' database
* @param projectId Cloudant username (set once at action update time)
* @param userId Cloudant password (set once at action update time)
* @param password Cloudant password (set once at action update time)
* @return The reference to a configured object storage instance
*/
function ObjectStorage(region, projectId, userId, password) {
var self = this;
if (region === "dallas") {
self.baseUrl = "https://dal.objectstorage.open.softlayer.com/v1/AUTH_" + projectId + "/";
} else if (region == "london") {
self.baseUrl = "https://lon.objectstorage.open.softlayer.com/v1/AUTH_" + projectId + "/";
} else {
throw new Error("Invalid Region");
}
self.authenticate = function(callback) {
request({
uri: "https://identity.open.softlayer.com/v3/auth/tokens",
method: 'POST',
json: {
"auth": {
"identity": {
"methods": [
"password"
],
"password": {
"user": {
"id": userId,
"password": password
}
}
},
"scope": {
"project": {
"id": projectId
}
}
}
}
}, function(err, response, body) {
if (!err) {
self.token = response.headers["x-subject-token"];
}
if (callback) {
callback(err, response, body);
}
});
};
self.downloadFile = function(container, file, outputStream, callback) {
request({
uri: self.baseUrl + container + "/" + file,
method: 'GET',
headers: {
"X-Auth-Token": self.token,
"Accept": "application/json"
}
}).pipe(outputStream).on('close', function() {
callback(null);
});
};
self.uploadFile = function(container, file, inputStream, callback) {
inputStream.pipe(
request({
uri: self.baseUrl + container + "/" + file,
method: 'PUT',
headers: {
"X-Auth-Token": self.token,
"Accept": "application/json"
}
}, function(err, response, body) {
callback(err);
}));
};
self.deleteFile = function(container, file, callback) {
request({
uri: self.baseUrl + container + "/" + file,
method: 'DELETE',
headers: {
"X-Auth-Token": self.token,
"Accept": "application/json"
}
}, function(err, response, body) {
callback(err);
});
};
}