@live-change/image-service 0.2.34 → 0.2.37

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 (3) hide show
  1. package/endpoint.js +36 -32
  2. package/image.js +68 -272
  3. package/package.json +2 -2
package/endpoint.js CHANGED
@@ -32,11 +32,11 @@ function fileExists(fn) {
32
32
  function delay(ms) {
33
33
  return new Promise((resolve, reject) => setTimeout(resolve, ms))
34
34
  }
35
- async function getImageMetadata(picture, version) {
35
+ async function getImageMetadata(image, version) {
36
36
  for(let t = 0; t < 5; t++) {
37
- const metadata = await Image.get(picture)
37
+ const metadata = await Image.get(image)
38
38
  console.log("MD", metadata)
39
- if(metadata.original && metadata[version]) return metadata
39
+ if(metadata) return metadata
40
40
  await delay(200 * Math.pow(2, t))
41
41
  }
42
42
  return null
@@ -47,32 +47,32 @@ function sanitizeImageId(id) {
47
47
  }
48
48
 
49
49
  async function handleImageGet(req, res, params) {
50
- const { picture, version } = params
51
- const metadata = await getImageMetadata(picture, version)
52
- console.log("PIC METADATA", picture, version, "=>", metadata)
50
+ const { image } = params
51
+ const metadata = await getImageMetadata(image)
52
+ console.log("PIC METADATA", image, "=>", metadata)
53
53
  if(!metadata) {
54
54
  res.status(404)
55
- res.send("Picture not found")
55
+ res.send("Image " + image + " not found")
56
56
  return
57
57
  }
58
- const imagePrefix = imagesPath + sanitizeImageId(picture) + '/' + version.replace(/[^a-zA-Z0-9-]/g,"_")
59
- const sourceFilePath = path.resolve(imagePrefix + '.' + metadata.original.extension)
60
- console.log("SOURCE PIC PATH", sourceFilePath)
58
+ const imagePrefix = imagesPath + sanitizeImageId(image) + '/'
59
+ const sourceFilePath = path.resolve(imagePrefix + 'original.' + metadata.extension)
60
+ console.log("SOURCE IMAGE PATH", sourceFilePath)
61
61
  if(!(await fileExists(sourceFilePath))) {
62
62
  res.status(404)
63
- console.error("PICTURE FILE NOT FOUND", sourceFilePath)
64
- res.send("Picture file not found")
63
+ console.error("IMAGE FILE NOT FOUND", sourceFilePath)
64
+ res.send("Image file not found")
65
65
  return
66
66
  }
67
67
 
68
68
  const kernel = "lanczos3"
69
69
 
70
70
  switch(params.type) {
71
- case "auto": {
72
- if(params.format && !isFormatsIdentical(params.format, metadata.original.extension)) {
73
- console.log("CONVERTING PICTURE!", metadata.original.extension, params.format)
71
+ case "original": {
72
+ if(params.format && !isFormatsIdentical(params.format, metadata.extension)) {
73
+ console.log("CONVERTING IMAGE!", metadata.extension, params.format)
74
74
  const normalized = normalizeFormat(params.format)
75
- const convertedFilePath = path.resolve(imagePrefix + '.' + normalized)
75
+ const convertedFilePath = path.resolve(imagePrefix + 'converted.' + normalized)
76
76
  if(!(await fileExists(convertedFilePath))) {
77
77
  await sharp(sourceFilePath).toFile(convertedFilePath)
78
78
  }
@@ -87,8 +87,8 @@ async function handleImageGet(req, res, params) {
87
87
  return
88
88
  }
89
89
  //if(width > metadata.original.width) return res.sendFile(sourceFilePath)
90
- const normalized = normalizeFormat(params.format || metadata.original.extension)
91
- const convertedFilePath = path.resolve(imagePrefix + `-width-${width}.${normalized}`)
90
+ const normalized = normalizeFormat(params.format || metadata.extension)
91
+ const convertedFilePath = path.resolve(imagePrefix + `width-${width}.${normalized}`)
92
92
  if(!(await fileExists(convertedFilePath))) {
93
93
  await sharp(sourceFilePath)
94
94
  .resize({
@@ -107,8 +107,8 @@ async function handleImageGet(req, res, params) {
107
107
  return
108
108
  }
109
109
  //if(height > metadata.original.height) return res.sendFile(sourceFilePath)
110
- const normalized = normalizeFormat(params.format || metadata.original.extension)
111
- const convertedFilePath = path.resolve(imagePrefix + `-height-${height}.${normalized}`)
110
+ const normalized = normalizeFormat(params.format || metadata.extension)
111
+ const convertedFilePath = path.resolve(imagePrefix + `height-${height}.${normalized}`)
112
112
  if(!(await fileExists(convertedFilePath))) {
113
113
  await sharp(sourceFilePath)
114
114
  .resize({
@@ -129,8 +129,8 @@ async function handleImageGet(req, res, params) {
129
129
  }
130
130
  //if(height > metadata.original.height) return res.sendFile(sourceFilePath)
131
131
  //if(width > metadata.original.width) return res.sendFile(sourceFilePath)
132
- const normalized = normalizeFormat(params.format || metadata.original.extension)
133
- const convertedFilePath = path.resolve(imagePrefix + `-rect-${width}-${height}.${normalized}`)
132
+ const normalized = normalizeFormat(params.format || metadata.extension)
133
+ const convertedFilePath = path.resolve(imagePrefix + `rect-${width}-${height}.${normalized}`)
134
134
  if(!(await fileExists(convertedFilePath))) {
135
135
  await sharp(sourceFilePath)
136
136
  .resize({
@@ -153,30 +153,34 @@ definition.endpoint({
153
153
  name: 'image',
154
154
  create() {
155
155
  const expressApp = express()
156
- expressApp.get('/:image/:version/width-:width.:format',
156
+ expressApp.get('/:image/width-:width.:format',
157
157
  (req, res) => handleImageGet(req, res, { ...req.params, type: "width" })
158
158
  )
159
- expressApp.get('/:image/:version/width-:width',
159
+ expressApp.get('/:image/width-:width',
160
160
  (req, res) => handleImageGet(req, res, { ...req.params, type: "width" })
161
161
  )
162
- expressApp.get('/:image/:version/height-:height.:format',
162
+ expressApp.get('/:image/height-:height.:format',
163
163
  (req, res) => handleImageGet(req, res, { ...req.params, type: "height" })
164
164
  )
165
- expressApp.get('/:image/:version/height-:height',
165
+ expressApp.get('/:image/height-:height',
166
166
  (req, res) => handleImageGet(req, res, { ...req.params, type: "height" })
167
167
  )
168
- expressApp.get('/:image/:version/rect-:width-:height.:format',
168
+ expressApp.get('/:image/rect-:width-:height.:format',
169
169
  (req, res) => handleImageGet(req, res, { ...req.params, type: "rect" })
170
170
  )
171
- expressApp.get('/:image/:version/rect-:width-:height',
171
+ expressApp.get('/:image/rect-:width-:height',
172
172
  (req, res) => handleImageGet(req, res, { ...req.params, type: "rect" })
173
173
  )
174
- expressApp.get('/:image/:version.:format',
175
- (req, res) => handleImageGet(req, res, { ...req.params, type: "auto" })
174
+ expressApp.get('/:image/.:format',
175
+ (req, res) => handleImageGet(req, res, { ...req.params, type: "original" })
176
176
  )
177
- expressApp.get('/:image/:version',
178
- (req, res) => handleImageGet(req, res, { ...req.params, type: "auto" })
177
+ expressApp.get('/:image',
178
+ (req, res) => handleImageGet(req, res, { ...req.params, type: "original" })
179
179
  )
180
+ expressApp.use('*', async (req, res) => {
181
+ res.writeHead(200, { "Content-Type": "text/plain" })
182
+ res.end("IMAGE!")
183
+ })
180
184
  return expressApp
181
185
  }
182
186
  })
package/image.js CHANGED
@@ -5,6 +5,19 @@ const config = definition.config
5
5
  const imagesPath = config.imagesPath || `./storage/images/`
6
6
  const uploadsPath = config.uploadsPath || `./storage/uploads/`
7
7
 
8
+ const cropInfo = {
9
+ type: Object,
10
+ properties: {
11
+ originalImage: {
12
+ type: String
13
+ },
14
+ x: { type: Number },
15
+ y: { type: Number },
16
+ zoom: { type: Number, defaultValue: 1 },
17
+ orientation: { type: Number }
18
+ }
19
+ }
20
+
8
21
  const Image = definition.model({
9
22
  name: "Image",
10
23
  itemOfAny: {
@@ -16,144 +29,34 @@ const Image = definition.model({
16
29
  fileName: {
17
30
  type: String
18
31
  },
19
- original: {
20
- type: Object,
21
- properties: {
22
- width: { type: Number },
23
- height: { type: Number },
24
- extension: { type: String }
25
- }
26
- },
27
- crop: {
28
- type: Object,
29
- properties: {
30
- x: { type: Number },
31
- y: { type: Number },
32
- width: { type: Number },
33
- height: { type: Number },
34
- zoom: { type: Number, defaultValue: 1 },
35
- orientation: {type: Number}
36
- }
37
- },
38
- purpose: {
39
- type: String,
32
+ width: {
33
+ type: Number,
40
34
  validation: ['nonEmpty']
41
- }
42
- }
43
- })
44
-
45
- const { move, copy, mkdir, rmdir } = require('./fsUtils')
46
- const sharp = require('sharp')
47
- const download = require('download')
48
-
49
- definition.action({
50
- name: "createEmptyImage",
51
- properties: {
52
- image: {
53
- type: Image
54
35
  },
55
- name: {
56
- type: String,
36
+ height: {
37
+ type: Number,
57
38
  validation: ['nonEmpty']
58
39
  },
59
- purpose: {
40
+ extension: {
60
41
  type: String,
61
42
  validation: ['nonEmpty']
62
43
  },
63
- ownerType: {
44
+ purpose: {
64
45
  type: String,
65
46
  validation: ['nonEmpty']
66
47
  },
67
- owner: {
68
- type: String,
69
- validation: ['nonEmpty']
70
- }
71
- },
72
- /// TODO: accessControl
73
- async execute({ image, name, purpose, owner, ownerType }, { client, service }, emit) {
74
- if(!image) {
75
- image = app.generateUid()
76
- } else {
77
- // TODO: check id source session
78
- const existing = await Image.get(image)
79
- if (existing) throw 'already_exists'
80
- }
81
-
82
- const dir = `${imagesPath}${image}`
83
-
84
- emit({
85
- type: "ownerOwnedImageCreated",
86
- image,
87
- identifiers: {
88
- owner, ownerType
89
- },
90
- data: {
91
- name, purpose,
92
- fileName: null,
93
- original: null,
94
- crop: null
95
- }
96
- })
97
-
98
- await mkdir(dir)
99
- await mkdir(`${dir}/originalCache`)
100
- await mkdir(`${dir}/cropCache`)
101
-
102
- return image
48
+ crop: cropInfo
103
49
  }
104
50
  })
105
51
 
106
- const Upload = definition.foreignModel('upload', 'Upload')
107
-
108
- definition.action({
109
- name: "uploadImage",
110
- properties: {
111
- image: {
112
- type: Image
113
- },
114
- original: {
115
- type: Object,
116
- properties: {
117
- width: { type: Number },
118
- height: { type: Number },
119
- upload: { type: Upload }
120
- }
121
- }
122
- },
123
- /// TODO: accessControl!
124
- waitForEvents: true,
125
- async execute({ image, original }, { client, service }, emit) {
126
- const uploadRow = await Upload.get(original.upload)
127
- if(!uploadRow) throw new Error("upload_not_found")
128
- if(uploadRow.state!='done') throw new Error("upload_not_done")
129
-
130
- let extension = uploadRow.fileName.match(/\.([A-Z0-9]+)$/i)[1].toLowerCase()
131
- if(extension == 'jpg') extension = "jpeg"
132
- const dir = `${imagesPath}${image}`
133
-
134
- emit({
135
- type: "ownerOwnedImageUpdated",
136
- image,
137
- data: {
138
- fileName: uploadRow.fileName,
139
- original: {
140
- width: original.width,
141
- height: original.height,
142
- extension
143
- },
144
- crop: null
145
- }
146
- })
52
+ const { move, copy, mkdir, rmdir } = require('./fsUtils')
53
+ const fs = require('fs')
54
+ const sharp = require('sharp')
55
+ const download = require('download')
147
56
 
148
- await move(`${uploadsPath}${uploadRow.id}`, `${dir}/original.${extension}`)
149
- await app.trigger({
150
- type: 'uploadUsed',
151
- upload: uploadRow.id
152
- })
57
+ fs.mkdirSync(imagesPath, { recursive: true })
153
58
 
154
- return image
155
- }
156
- })
59
+ const Upload = definition.foreignModel('upload', 'Upload')
157
60
 
158
61
  definition.action({
159
62
  name: "createImage",
@@ -165,13 +68,17 @@ definition.action({
165
68
  type: String,
166
69
  validation: ['nonEmpty']
167
70
  },
168
- original: {
169
- type: Object,
170
- properties: {
171
- width: { type: Number },
172
- height: { type: Number },
173
- upload: { type: Upload }
174
- }
71
+ width: {
72
+ type: Number,
73
+ validation: ['nonEmpty']
74
+ },
75
+ height: {
76
+ type: Number,
77
+ validation: ['nonEmpty']
78
+ },
79
+ upload: {
80
+ type: Upload,
81
+ validation: ['nonEmpty']
175
82
  },
176
83
  purpose: {
177
84
  type: String,
@@ -184,11 +91,12 @@ definition.action({
184
91
  owner: {
185
92
  type: String,
186
93
  validation: ['nonEmpty']
187
- }
94
+ },
95
+ crop: cropInfo
188
96
  },
189
97
  /// TODO: accessControl!
190
98
  waitForEvents: true,
191
- async execute({ image, name, original, purpose, owner, ownerType }, { client, service }, emit) {
99
+ async execute({ image, name, width, height, upload, purpose, owner, ownerType, crop }, { client, service }, emit) {
192
100
  if(!image) {
193
101
  image = app.generateUid()
194
102
  } else {
@@ -196,7 +104,7 @@ definition.action({
196
104
  const existing = await Image.get(image)
197
105
  if (existing) throw 'already_exists'
198
106
  }
199
- const uploadRow = await Upload.get(original.upload)
107
+ const uploadRow = await Upload.get(upload)
200
108
  if(!uploadRow) throw new Error("upload_not_found")
201
109
  if(uploadRow.state!='done') throw new Error("upload_not_done")
202
110
 
@@ -211,22 +119,13 @@ definition.action({
211
119
  owner, ownerType
212
120
  },
213
121
  data: {
214
- name,
215
- purpose,
122
+ name, purpose,
216
123
  fileName: uploadRow.fileName,
217
- original: {
218
- width: original.width,
219
- height: original.height,
220
- extension
221
- },
222
- crop: null,
223
- owner: client.user
124
+ width, height, extension, crop
224
125
  }
225
126
  })
226
127
 
227
128
  await mkdir(dir)
228
- await mkdir(`${dir}/originalCache`)
229
- await mkdir(`${dir}/cropCache`)
230
129
  await move(`${uploadsPath}${uploadRow.id}`, `${dir}/original.${extension}`)
231
130
 
232
131
  await app.trigger({
@@ -238,100 +137,6 @@ definition.action({
238
137
  }
239
138
  })
240
139
 
241
- definition.action({
242
- name: "cropImage",
243
- properties: {
244
- image: {
245
- type: Image
246
- },
247
- crop: {
248
- type: Object,
249
- properties: {
250
- x: {type: Number},
251
- y: {type: Number},
252
- width: {type: Number},
253
- height: {type: Number},
254
- zoom: {type: Number, defaultValue: 1},
255
- orientation: {type: Number}
256
- }
257
- },
258
- upload: {type: Upload}
259
- },
260
- /// TODO: accessControl!
261
- waitForEvents: true,
262
- async execute({ image, crop, upload }, {client, service}, emit) {
263
- const imageRow = await Image.get(image)
264
- if(!imageRow) throw new Error("not_found")
265
-
266
- const uploadRow = await Upload.get(upload)
267
-
268
- console.log("UPLOAD CROP", uploadRow)
269
-
270
- if(!uploadRow) throw new Error("upload_not_found")
271
- if(uploadRow.state != 'done') throw new Error("upload_not_done")
272
-
273
- console.log("CURRENT IMAGE ROW", image, imageRow)
274
- if(!imageRow.crop) { // first crop
275
- const dir = `${imagesPath}${image}`
276
- let extension = uploadRow.fileName.match(/\.([A-Z0-9]+)$/i)[1].toLowerCase()
277
- if(extension == 'jpg') extension = "jpeg"
278
-
279
- await move(`${uploadsPath}${uploadRow.id}`, `${dir}/crop.${extension}`)
280
- await app.dao.request(['database', 'delete'], app.databaseName, 'uploads', uploadRow.id)
281
-
282
- emit({
283
- type: "ownerOwnedImageUpdated",
284
- image,
285
- data: {
286
- crop
287
- }
288
- })
289
-
290
- return image
291
- } else { // next crop - need to copy image
292
- const newImage = app.generateUid()
293
-
294
- const dir = `${imagesPath}${image}`
295
- const newDir = `${imagesPath}${newImage}`
296
-
297
- await mkdir(newDir)
298
- await mkdir(`${newDir}/originalCache`)
299
- await mkdir(`${newDir}/cropCache`)
300
- await move(`${dir}/original.${imageRow.original.extension}`,
301
- `${newDir}/original.${imageRow.original.extension}`)
302
-
303
- let extension = uploadRow.fileName.match(/\.([A-Z0-9]+)$/i)[1].toLowerCase()
304
- if(extension == 'jpg') extension = "jpeg"
305
-
306
- await move(`../../storage/uploads/${uploadRow.id}`, `${newDir}/crop.${extension}`)
307
-
308
- await app.trigger({
309
- type: 'uploadUsed',
310
- upload: uploadRow.id
311
- })
312
-
313
- const { owner, ownerType } = imageRow
314
-
315
- emit({
316
- type: "ownerOwnedImageCreated",
317
- image,
318
- identifiers: {
319
- owner, ownerType
320
- },
321
- data: {
322
- name: imageRow.name,
323
- purpose: imageRow.purpose,
324
- fileName: uploadRow.fileName,
325
- original: imageRow.original,
326
- crop
327
- }
328
- })
329
-
330
- return newImage
331
- }
332
- }
333
- })
334
-
335
140
  definition.trigger({
336
141
  name: "createImageFromUrl",
337
142
  properties: {
@@ -361,7 +166,7 @@ definition.trigger({
361
166
  }
362
167
  },
363
168
  waitForEvents: true,
364
- async execute({ name, purpose, url, cropped, owner, ownerType }, { service, client }, emit) {
169
+ async execute({ name, purpose, url, owner, ownerType }, { service, client }, emit) {
365
170
  const image = app.generateUid()
366
171
 
367
172
  const downloadPath = `${uploadsPath}download_${image}`
@@ -369,54 +174,45 @@ definition.trigger({
369
174
 
370
175
  const metadata = await sharp(downloadPath).metadata()
371
176
 
372
- let data = {
373
- name,
374
- purpose,
375
- fileName: url.split('/').pop(),
376
- original: {
377
- width: metadata.width,
378
- height: metadata.height,
379
- extension: metadata.format
380
- },
381
- crop: null
382
- }
383
-
384
- if(cropped) {
385
- data.crop = {
386
- x: 0,
387
- y: 0,
388
- width: metadata.width,
389
- height: metadata.height,
390
- zoom: 1,
391
- orientation: 0
392
- }
393
- }
394
-
395
- emit({
396
- type: "ImageCreated",
397
- image,
398
- data
399
- })
400
-
401
177
  emit({
402
178
  type: "ownerOwnedImageCreated",
403
179
  image,
404
180
  identifiers: {
405
181
  owner, ownerType
406
182
  },
407
- data
183
+ data: {
184
+ name, purpose,
185
+ fileName: url.split('/').pop(),
186
+ width: metadata.width,
187
+ height: metadata.height,
188
+ extension: metadata.format,
189
+ crop: null
190
+ }
408
191
  })
409
192
 
410
193
  const dir = `${imagesPath}/${image}`
411
194
 
412
195
  await mkdir(dir)
413
- await mkdir(`${dir}/originalCache`)
414
- await mkdir(`${dir}/cropCache`)
415
196
  await move(downloadPath, `${dir}/original.${metadata.format}`)
416
- if(cropped) await copy(`${dir}/original.${metadata.format}`, `${dir}/crop.${metadata.format}`)
417
197
 
418
198
  return image
419
199
  }
420
200
  })
421
201
 
202
+ definition.view({
203
+ name: 'image',
204
+ properties: {
205
+ image: {
206
+ type: Image,
207
+ validation: ['nonEmpty']
208
+ }
209
+ },
210
+ returns: {
211
+ type: Image
212
+ },
213
+ daoPath({ image }, { client, context }) {
214
+ return Image.path( image )
215
+ }
216
+ })
217
+
422
218
  module.exports = { Image }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-change/image-service",
3
- "version": "0.2.34",
3
+ "version": "0.2.37",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -27,5 +27,5 @@
27
27
  "pluralize": "8.0.0",
28
28
  "sharp": "^0.30.6"
29
29
  },
30
- "gitHead": "45f52d6c7586d0eaa51b3205c6635a33e32eef6b"
30
+ "gitHead": "f3bc615d20a0112c7cc76d55ba1cbefb53b84f01"
31
31
  }