@live-change/image-service 0.2.33 → 0.2.36
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/clientConfig.js +24 -0
- package/endpoint.js +173 -35
- package/fsUtils.js +1 -0
- package/image.js +95 -268
- package/index.js +2 -0
- package/package.json +5 -3
package/clientConfig.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const definition = require('./definition.js')
|
|
2
|
+
|
|
3
|
+
const config = definition.config
|
|
4
|
+
const uploadConfig = config.upload || {}
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
maxUploadSize = 10 * 1024 * 1024,
|
|
8
|
+
maxUploadWidth = 2048,
|
|
9
|
+
maxUploadHeight = 2048,
|
|
10
|
+
maxProcessableSize = 50 * 1024 * 1025,
|
|
11
|
+
maxProcessableWidth = 10000,
|
|
12
|
+
maxProcessableHeight = 10000,
|
|
13
|
+
maxProcessablePixels = 6000*6000
|
|
14
|
+
} = uploadConfig
|
|
15
|
+
|
|
16
|
+
definition.clientConfig = {
|
|
17
|
+
maxUploadSize,
|
|
18
|
+
maxUploadWidth,
|
|
19
|
+
maxUploadHeight,
|
|
20
|
+
maxProcessableSize,
|
|
21
|
+
maxProcessableWidth,
|
|
22
|
+
maxProcessableHeight,
|
|
23
|
+
maxProcessablePixels
|
|
24
|
+
}
|
package/endpoint.js
CHANGED
|
@@ -1,8 +1,151 @@
|
|
|
1
1
|
const app = require("@live-change/framework").app()
|
|
2
2
|
const definition = require('./definition.js')
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
|
|
4
|
+
const { Image } = require('./image.js')
|
|
5
|
+
|
|
6
|
+
const fs = require("fs")
|
|
7
|
+
const path = require("path")
|
|
8
|
+
const sharp = require('sharp')
|
|
9
|
+
|
|
10
|
+
const config = definition.config
|
|
11
|
+
|
|
12
|
+
const imagesPath = config.imagesPath || `./storage/images/`
|
|
13
|
+
|
|
14
|
+
function normalizeFormat(f1) {
|
|
15
|
+
f1 = f1.toLowerCase().trim()
|
|
16
|
+
if(f1 == 'jpg') f1 = 'jpeg'
|
|
17
|
+
return f1
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isFormatsIdentical(f1, f2) {
|
|
21
|
+
return normalizeFormat(f1) == normalizeFormat(f2)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function fileExists(fn) {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
fs.access(fn, fs.F_OK, (err) => {
|
|
27
|
+
resolve(!err)
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function delay(ms) {
|
|
33
|
+
return new Promise((resolve, reject) => setTimeout(resolve, ms))
|
|
34
|
+
}
|
|
35
|
+
async function getImageMetadata(image, version) {
|
|
36
|
+
for(let t = 0; t < 5; t++) {
|
|
37
|
+
const metadata = await Image.get(image)
|
|
38
|
+
console.log("MD", metadata)
|
|
39
|
+
if(metadata) return metadata
|
|
40
|
+
await delay(200 * Math.pow(2, t))
|
|
41
|
+
}
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function sanitizeImageId(id) {
|
|
46
|
+
return id.replace(/[^a-zA-Z0-9\[\]@\.-]/g,"_")
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function handleImageGet(req, res, params) {
|
|
50
|
+
const { image } = params
|
|
51
|
+
const metadata = await getImageMetadata(image)
|
|
52
|
+
console.log("PIC METADATA", image, "=>", metadata)
|
|
53
|
+
if(!metadata) {
|
|
54
|
+
res.status(404)
|
|
55
|
+
res.send("Image " + image + " not found")
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
const imagePrefix = imagesPath + sanitizeImageId(image) + '/'
|
|
59
|
+
const sourceFilePath = path.resolve(imagePrefix + 'original.' + metadata.extension)
|
|
60
|
+
console.log("SOURCE IMAGE PATH", sourceFilePath)
|
|
61
|
+
if(!(await fileExists(sourceFilePath))) {
|
|
62
|
+
res.status(404)
|
|
63
|
+
console.error("IMAGE FILE NOT FOUND", sourceFilePath)
|
|
64
|
+
res.send("Image file not found")
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const kernel = "lanczos3"
|
|
69
|
+
|
|
70
|
+
switch(params.type) {
|
|
71
|
+
case "original": {
|
|
72
|
+
if(params.format && !isFormatsIdentical(params.format, metadata.extension)) {
|
|
73
|
+
console.log("CONVERTING IMAGE!", metadata.extension, params.format)
|
|
74
|
+
const normalized = normalizeFormat(params.format)
|
|
75
|
+
const convertedFilePath = path.resolve(imagePrefix + 'converted.' + normalized)
|
|
76
|
+
if(!(await fileExists(convertedFilePath))) {
|
|
77
|
+
await sharp(sourceFilePath).toFile(convertedFilePath)
|
|
78
|
+
}
|
|
79
|
+
res.sendFile(convertedFilePath)
|
|
80
|
+
} else res.sendFile(sourceFilePath)
|
|
81
|
+
} break;
|
|
82
|
+
case "width": {
|
|
83
|
+
const width = +params.width
|
|
84
|
+
if(!(width > 0)) {
|
|
85
|
+
res.status(400)
|
|
86
|
+
res.send("Bad parameter value")
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
//if(width > metadata.original.width) return res.sendFile(sourceFilePath)
|
|
90
|
+
const normalized = normalizeFormat(params.format || metadata.extension)
|
|
91
|
+
const convertedFilePath = path.resolve(imagePrefix + `width-${width}.${normalized}`)
|
|
92
|
+
if(!(await fileExists(convertedFilePath))) {
|
|
93
|
+
await sharp(sourceFilePath)
|
|
94
|
+
.resize({
|
|
95
|
+
width,
|
|
96
|
+
kernel
|
|
97
|
+
})
|
|
98
|
+
.toFile(convertedFilePath)
|
|
99
|
+
}
|
|
100
|
+
res.sendFile(convertedFilePath)
|
|
101
|
+
} break;
|
|
102
|
+
case "height": {
|
|
103
|
+
const height = +params.height
|
|
104
|
+
if(!(height > 0)) {
|
|
105
|
+
res.status(400)
|
|
106
|
+
res.send("Bad parameter value")
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
//if(height > metadata.original.height) return res.sendFile(sourceFilePath)
|
|
110
|
+
const normalized = normalizeFormat(params.format || metadata.extension)
|
|
111
|
+
const convertedFilePath = path.resolve(imagePrefix + `height-${height}.${normalized}`)
|
|
112
|
+
if(!(await fileExists(convertedFilePath))) {
|
|
113
|
+
await sharp(sourceFilePath)
|
|
114
|
+
.resize({
|
|
115
|
+
height,
|
|
116
|
+
kernel
|
|
117
|
+
})
|
|
118
|
+
.toFile(convertedFilePath)
|
|
119
|
+
}
|
|
120
|
+
res.sendFile(convertedFilePath)
|
|
121
|
+
} break;
|
|
122
|
+
case "rect": {
|
|
123
|
+
const width = +params.width
|
|
124
|
+
const height = +params.height
|
|
125
|
+
if(!(height > 0 && width>0)) {
|
|
126
|
+
res.status(400)
|
|
127
|
+
res.send("Bad parameter value")
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
//if(height > metadata.original.height) return res.sendFile(sourceFilePath)
|
|
131
|
+
//if(width > metadata.original.width) return res.sendFile(sourceFilePath)
|
|
132
|
+
const normalized = normalizeFormat(params.format || metadata.extension)
|
|
133
|
+
const convertedFilePath = path.resolve(imagePrefix + `rect-${width}-${height}.${normalized}`)
|
|
134
|
+
if(!(await fileExists(convertedFilePath))) {
|
|
135
|
+
await sharp(sourceFilePath)
|
|
136
|
+
.resize({
|
|
137
|
+
width, height,
|
|
138
|
+
fit: sharp.fit.cover,
|
|
139
|
+
position: sharp.strategy.attention,
|
|
140
|
+
kernel
|
|
141
|
+
})
|
|
142
|
+
.toFile(convertedFilePath)
|
|
143
|
+
}
|
|
144
|
+
res.sendFile(convertedFilePath)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
6
149
|
|
|
7
150
|
const express = require("express")
|
|
8
151
|
|
|
@@ -10,39 +153,34 @@ definition.endpoint({
|
|
|
10
153
|
name: 'image',
|
|
11
154
|
create() {
|
|
12
155
|
const expressApp = express()
|
|
13
|
-
expressApp.get('/:image
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
expressApp.get('/:image
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
expressApp.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
})
|
|
41
|
-
expressApp.get('/:image/:version', (req, res) => handleImageGet(req, res, {
|
|
42
|
-
...req.params,
|
|
43
|
-
type: "auto"
|
|
44
|
-
}))
|
|
45
|
-
|
|
156
|
+
expressApp.get('/:image/width-:width.:format',
|
|
157
|
+
(req, res) => handleImageGet(req, res, { ...req.params, type: "width" })
|
|
158
|
+
)
|
|
159
|
+
expressApp.get('/:image/width-:width',
|
|
160
|
+
(req, res) => handleImageGet(req, res, { ...req.params, type: "width" })
|
|
161
|
+
)
|
|
162
|
+
expressApp.get('/:image/height-:height.:format',
|
|
163
|
+
(req, res) => handleImageGet(req, res, { ...req.params, type: "height" })
|
|
164
|
+
)
|
|
165
|
+
expressApp.get('/:image/height-:height',
|
|
166
|
+
(req, res) => handleImageGet(req, res, { ...req.params, type: "height" })
|
|
167
|
+
)
|
|
168
|
+
expressApp.get('/:image/rect-:width-:height.:format',
|
|
169
|
+
(req, res) => handleImageGet(req, res, { ...req.params, type: "rect" })
|
|
170
|
+
)
|
|
171
|
+
expressApp.get('/:image/rect-:width-:height',
|
|
172
|
+
(req, res) => handleImageGet(req, res, { ...req.params, type: "rect" })
|
|
173
|
+
)
|
|
174
|
+
expressApp.get('/:image/.:format',
|
|
175
|
+
(req, res) => handleImageGet(req, res, { ...req.params, type: "original" })
|
|
176
|
+
)
|
|
177
|
+
expressApp.get('/:image',
|
|
178
|
+
(req, res) => handleImageGet(req, res, { ...req.params, type: "original" })
|
|
179
|
+
)
|
|
180
|
+
expressApp.use('*', async (req, res) => {
|
|
181
|
+
res.writeHead(200, { "Content-Type": "text/plain" })
|
|
182
|
+
res.end("IMAGE!")
|
|
183
|
+
})
|
|
46
184
|
return expressApp
|
|
47
185
|
}
|
|
48
186
|
})
|
package/fsUtils.js
CHANGED
package/image.js
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
const app = require("@live-change/framework").app()
|
|
2
2
|
const definition = require('./definition.js')
|
|
3
|
+
const config = definition.config
|
|
3
4
|
|
|
4
|
-
const
|
|
5
|
-
const
|
|
5
|
+
const imagesPath = config.imagesPath || `./storage/images/`
|
|
6
|
+
const uploadsPath = config.uploadsPath || `./storage/uploads/`
|
|
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
|
+
}
|
|
6
20
|
|
|
7
21
|
const Image = definition.model({
|
|
8
22
|
name: "Image",
|
|
@@ -15,145 +29,56 @@ const Image = definition.model({
|
|
|
15
29
|
fileName: {
|
|
16
30
|
type: String
|
|
17
31
|
},
|
|
18
|
-
|
|
19
|
-
type:
|
|
20
|
-
|
|
21
|
-
width: { type: Number },
|
|
22
|
-
height: { type: Number },
|
|
23
|
-
extension: { type: String }
|
|
24
|
-
}
|
|
32
|
+
width: {
|
|
33
|
+
type: Number,
|
|
34
|
+
validation: ['nonEmpty']
|
|
25
35
|
},
|
|
26
|
-
|
|
27
|
-
type:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
zoom: { type: Number, defaultValue: 1 },
|
|
34
|
-
orientation: {type: Number}
|
|
35
|
-
}
|
|
36
|
+
height: {
|
|
37
|
+
type: Number,
|
|
38
|
+
validation: ['nonEmpty']
|
|
39
|
+
},
|
|
40
|
+
extension: {
|
|
41
|
+
type: String,
|
|
42
|
+
validation: ['nonEmpty']
|
|
36
43
|
},
|
|
37
44
|
purpose: {
|
|
38
45
|
type: String,
|
|
39
46
|
validation: ['nonEmpty']
|
|
40
|
-
}
|
|
47
|
+
},
|
|
48
|
+
crop: cropInfo
|
|
41
49
|
}
|
|
42
50
|
})
|
|
43
51
|
|
|
44
52
|
const { move, copy, mkdir, rmdir } = require('./fsUtils')
|
|
53
|
+
const fs = require('fs')
|
|
45
54
|
const sharp = require('sharp')
|
|
46
55
|
const download = require('download')
|
|
47
56
|
|
|
48
|
-
|
|
49
|
-
name: "createEmptyImage",
|
|
50
|
-
properties: {
|
|
51
|
-
name: {
|
|
52
|
-
type: String,
|
|
53
|
-
validation: ['nonEmpty']
|
|
54
|
-
},
|
|
55
|
-
purpose: {
|
|
56
|
-
type: String,
|
|
57
|
-
validation: ['nonEmpty']
|
|
58
|
-
},
|
|
59
|
-
ownerType: {
|
|
60
|
-
type: String,
|
|
61
|
-
validation: ['nonEmpty']
|
|
62
|
-
},
|
|
63
|
-
owner: {
|
|
64
|
-
type: String,
|
|
65
|
-
validation: ['nonEmpty']
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
/// TODO: accessControl
|
|
69
|
-
async execute({ name, purpose, owner, ownerType }, { client, service }, emit) {
|
|
70
|
-
const image = app.generateUid()
|
|
71
|
-
|
|
72
|
-
const dir = `${storageDir}${image}`
|
|
73
|
-
|
|
74
|
-
emit({
|
|
75
|
-
type: "ownerOwnedImageCreated",
|
|
76
|
-
image,
|
|
77
|
-
identifiers: {
|
|
78
|
-
owner, ownerType
|
|
79
|
-
},
|
|
80
|
-
data: {
|
|
81
|
-
name, purpose,
|
|
82
|
-
fileName: null,
|
|
83
|
-
original: null,
|
|
84
|
-
crop: null
|
|
85
|
-
}
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
await mkdir(dir)
|
|
89
|
-
await mkdir(`${dir}/originalCache`)
|
|
90
|
-
await mkdir(`${dir}/cropCache`)
|
|
57
|
+
fs.mkdirSync(imagesPath, { recursive: true })
|
|
91
58
|
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
})
|
|
59
|
+
const Upload = definition.foreignModel('upload', 'Upload')
|
|
95
60
|
|
|
96
61
|
definition.action({
|
|
97
|
-
name: "
|
|
62
|
+
name: "createImage",
|
|
98
63
|
properties: {
|
|
99
64
|
image: {
|
|
100
65
|
type: Image
|
|
101
66
|
},
|
|
102
|
-
original: {
|
|
103
|
-
type: Object,
|
|
104
|
-
properties: {
|
|
105
|
-
width: { type: Number },
|
|
106
|
-
height: { type: Number },
|
|
107
|
-
uploadId: { type: String }
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
|
-
/// TODO: accessControl!
|
|
112
|
-
waitForEvents: true,
|
|
113
|
-
async execute({ image, original }, { client, service }, emit) {
|
|
114
|
-
const upload = await app.dao.get(['database', 'tableObject', app.databaseName, 'uploads', original.uploadId])
|
|
115
|
-
if(!upload) throw new Error("upload_not_found")
|
|
116
|
-
if(upload.state!='done') throw new Error("upload_not_done")
|
|
117
|
-
|
|
118
|
-
let extension = upload.fileName.match(/\.([A-Z0-9]+)$/i)[1].toLowerCase()
|
|
119
|
-
if(extension == 'jpg') extension = "jpeg"
|
|
120
|
-
const dir = `${storageDir}${image}`
|
|
121
|
-
|
|
122
|
-
emit({
|
|
123
|
-
type: "ownerOwnedImageUpdated",
|
|
124
|
-
image,
|
|
125
|
-
data: {
|
|
126
|
-
fileName: upload.fileName,
|
|
127
|
-
original: {
|
|
128
|
-
width: original.width,
|
|
129
|
-
height: original.height,
|
|
130
|
-
extension
|
|
131
|
-
},
|
|
132
|
-
crop: null
|
|
133
|
-
}
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
await move(`${uploadsDir}${upload.id}`, `${dir}/original.${extension}`)
|
|
137
|
-
await app.dao.request(['database', 'delete'], app.databaseName, 'uploads', upload.id)
|
|
138
|
-
|
|
139
|
-
return image
|
|
140
|
-
}
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
definition.action({
|
|
144
|
-
name: "createImage",
|
|
145
|
-
properties: {
|
|
146
67
|
name: {
|
|
147
68
|
type: String,
|
|
148
69
|
validation: ['nonEmpty']
|
|
149
70
|
},
|
|
150
|
-
|
|
151
|
-
type:
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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']
|
|
157
82
|
},
|
|
158
83
|
purpose: {
|
|
159
84
|
type: String,
|
|
@@ -166,20 +91,26 @@ definition.action({
|
|
|
166
91
|
owner: {
|
|
167
92
|
type: String,
|
|
168
93
|
validation: ['nonEmpty']
|
|
169
|
-
}
|
|
94
|
+
},
|
|
95
|
+
crop: cropInfo
|
|
170
96
|
},
|
|
171
97
|
/// TODO: accessControl!
|
|
172
98
|
waitForEvents: true,
|
|
173
|
-
async execute({ name,
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
99
|
+
async execute({ image, name, width, height, upload, purpose, owner, ownerType, crop }, { client, service }, emit) {
|
|
100
|
+
if(!image) {
|
|
101
|
+
image = app.generateUid()
|
|
102
|
+
} else {
|
|
103
|
+
// TODO: check id source session
|
|
104
|
+
const existing = await Image.get(image)
|
|
105
|
+
if (existing) throw 'already_exists'
|
|
106
|
+
}
|
|
107
|
+
const uploadRow = await Upload.get(upload)
|
|
108
|
+
if(!uploadRow) throw new Error("upload_not_found")
|
|
109
|
+
if(uploadRow.state!='done') throw new Error("upload_not_done")
|
|
179
110
|
|
|
180
|
-
let extension =
|
|
111
|
+
let extension = uploadRow.fileName.match(/\.([A-Z0-9]+)$/i)[1].toLowerCase()
|
|
181
112
|
if(extension == 'jpg') extension = "jpeg"
|
|
182
|
-
const dir = `${
|
|
113
|
+
const dir = `${imagesPath}${image}`
|
|
183
114
|
|
|
184
115
|
emit({
|
|
185
116
|
type: "ownerOwnedImageCreated",
|
|
@@ -188,116 +119,21 @@ definition.action({
|
|
|
188
119
|
owner, ownerType
|
|
189
120
|
},
|
|
190
121
|
data: {
|
|
191
|
-
name,
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
original: {
|
|
195
|
-
width: original.width,
|
|
196
|
-
height: original.height,
|
|
197
|
-
extension
|
|
198
|
-
},
|
|
199
|
-
crop: null,
|
|
200
|
-
owner: client.user
|
|
122
|
+
name, purpose,
|
|
123
|
+
fileName: uploadRow.fileName,
|
|
124
|
+
width, height, extension, crop
|
|
201
125
|
}
|
|
202
126
|
})
|
|
203
127
|
|
|
204
128
|
await mkdir(dir)
|
|
205
|
-
await
|
|
206
|
-
await mkdir(`${dir}/cropCache`)
|
|
207
|
-
await move(`${uploadsDir}${upload.id}`, `${dir}/original.${extension}`)
|
|
208
|
-
await app.dao.request(['database', 'delete'], app.databaseName, 'uploads', upload.id)
|
|
209
|
-
|
|
210
|
-
return image
|
|
211
|
-
}
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
definition.action({
|
|
215
|
-
name: "cropImage",
|
|
216
|
-
properties: {
|
|
217
|
-
image: {
|
|
218
|
-
type: Image
|
|
219
|
-
},
|
|
220
|
-
crop: {
|
|
221
|
-
type: Object,
|
|
222
|
-
properties: {
|
|
223
|
-
x: {type: Number},
|
|
224
|
-
y: {type: Number},
|
|
225
|
-
width: {type: Number},
|
|
226
|
-
height: {type: Number},
|
|
227
|
-
zoom: {type: Number, defaultValue: 1},
|
|
228
|
-
orientation: {type: Number}
|
|
229
|
-
}
|
|
230
|
-
},
|
|
231
|
-
uploadId: {type: String}
|
|
232
|
-
},
|
|
233
|
-
/// TODO: accessControl!
|
|
234
|
-
waitForEvents: true,
|
|
235
|
-
async execute({ image, crop, uploadId }, {client, service}, emit) {
|
|
236
|
-
const imageRow = await Image.get(image)
|
|
237
|
-
if(!imageRow) throw new Error("not_found")
|
|
238
|
-
|
|
239
|
-
const upload = await app.dao.get(['database', 'tableObject', app.databaseName, 'uploads', uploadId])
|
|
240
|
-
|
|
241
|
-
console.log("UPLOAD CROP", upload)
|
|
242
|
-
|
|
243
|
-
if(!upload) throw new Error("upload_not_found")
|
|
244
|
-
if(upload.state != 'done') throw new Error("upload_not_done")
|
|
245
|
-
|
|
246
|
-
console.log("CURRENT IMAGE ROW", image, imageRow)
|
|
247
|
-
if(!imageRow.crop) { // first crop
|
|
248
|
-
const dir = `${storageDir}${image}`
|
|
249
|
-
let extension = upload.fileName.match(/\.([A-Z0-9]+)$/i)[1].toLowerCase()
|
|
250
|
-
if(extension == 'jpg') extension = "jpeg"
|
|
251
|
-
|
|
252
|
-
await move(`${uploadsDir}${upload.id}`, `${dir}/crop.${extension}`)
|
|
253
|
-
await app.dao.request(['database', 'delete'], app.databaseName, 'uploads', upload.id)
|
|
254
|
-
|
|
255
|
-
emit({
|
|
256
|
-
type: "ownerOwnedImageUpdated",
|
|
257
|
-
image,
|
|
258
|
-
data: {
|
|
259
|
-
crop
|
|
260
|
-
}
|
|
261
|
-
})
|
|
262
|
-
|
|
263
|
-
return image
|
|
264
|
-
} else { // next crop - need to copy image
|
|
265
|
-
const newImage = app.generateUid()
|
|
266
|
-
|
|
267
|
-
const dir = `${storageDir}${image}`
|
|
268
|
-
const newDir = `${storageDir}${newImage}`
|
|
129
|
+
await move(`${uploadsPath}${uploadRow.id}`, `${dir}/original.${extension}`)
|
|
269
130
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
`${newDir}/original.${imageRow.original.extension}`)
|
|
275
|
-
|
|
276
|
-
let extension = upload.fileName.match(/\.([A-Z0-9]+)$/i)[1].toLowerCase()
|
|
277
|
-
if(extension == 'jpg') extension = "jpeg"
|
|
278
|
-
|
|
279
|
-
await move(`../../storage/uploads/${upload.id}`, `${newDir}/crop.${extension}`)
|
|
280
|
-
await app.dao.request(['database', 'delete'], app.databaseName, 'uploads', upload.id)
|
|
281
|
-
|
|
282
|
-
const { owner, ownerType } = imageRow
|
|
283
|
-
|
|
284
|
-
emit({
|
|
285
|
-
type: "ownerOwnedImageCreated",
|
|
286
|
-
image,
|
|
287
|
-
identifiers: {
|
|
288
|
-
owner, ownerType
|
|
289
|
-
},
|
|
290
|
-
data: {
|
|
291
|
-
name: imageRow.name,
|
|
292
|
-
purpose: imageRow.purpose,
|
|
293
|
-
fileName: upload.fileName,
|
|
294
|
-
original: imageRow.original,
|
|
295
|
-
crop
|
|
296
|
-
}
|
|
297
|
-
})
|
|
131
|
+
await app.trigger({
|
|
132
|
+
type: 'uploadUsed',
|
|
133
|
+
upload: uploadRow.id
|
|
134
|
+
})
|
|
298
135
|
|
|
299
|
-
|
|
300
|
-
}
|
|
136
|
+
return image
|
|
301
137
|
}
|
|
302
138
|
})
|
|
303
139
|
|
|
@@ -330,62 +166,53 @@ definition.trigger({
|
|
|
330
166
|
}
|
|
331
167
|
},
|
|
332
168
|
waitForEvents: true,
|
|
333
|
-
async execute({ name, purpose, url,
|
|
169
|
+
async execute({ name, purpose, url, owner, ownerType }, { service, client }, emit) {
|
|
334
170
|
const image = app.generateUid()
|
|
335
171
|
|
|
336
|
-
const downloadPath = `${
|
|
337
|
-
await download(url,
|
|
172
|
+
const downloadPath = `${uploadsPath}download_${image}`
|
|
173
|
+
await download(url, uploadsPath, { filename: `download_${image}` })
|
|
338
174
|
|
|
339
175
|
const metadata = await sharp(downloadPath).metadata()
|
|
340
176
|
|
|
341
|
-
let data = {
|
|
342
|
-
name,
|
|
343
|
-
purpose,
|
|
344
|
-
fileName: url.split('/').pop(),
|
|
345
|
-
original: {
|
|
346
|
-
width: metadata.width,
|
|
347
|
-
height: metadata.height,
|
|
348
|
-
extension: metadata.format
|
|
349
|
-
},
|
|
350
|
-
crop: null
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
if(cropped) {
|
|
354
|
-
data.crop = {
|
|
355
|
-
x: 0,
|
|
356
|
-
y: 0,
|
|
357
|
-
width: metadata.width,
|
|
358
|
-
height: metadata.height,
|
|
359
|
-
zoom: 1,
|
|
360
|
-
orientation: 0
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
emit({
|
|
365
|
-
type: "ImageCreated",
|
|
366
|
-
image,
|
|
367
|
-
data
|
|
368
|
-
})
|
|
369
|
-
|
|
370
177
|
emit({
|
|
371
178
|
type: "ownerOwnedImageCreated",
|
|
372
179
|
image,
|
|
373
180
|
identifiers: {
|
|
374
181
|
owner, ownerType
|
|
375
182
|
},
|
|
376
|
-
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
|
+
}
|
|
377
191
|
})
|
|
378
192
|
|
|
379
|
-
const dir = `${
|
|
193
|
+
const dir = `${imagesPath}/${image}`
|
|
380
194
|
|
|
381
195
|
await mkdir(dir)
|
|
382
|
-
await mkdir(`${dir}/originalCache`)
|
|
383
|
-
await mkdir(`${dir}/cropCache`)
|
|
384
196
|
await move(downloadPath, `${dir}/original.${metadata.format}`)
|
|
385
|
-
if(cropped) await copy(`${dir}/original.${metadata.format}`, `${dir}/crop.${metadata.format}`)
|
|
386
197
|
|
|
387
198
|
return image
|
|
388
199
|
}
|
|
389
200
|
})
|
|
390
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
|
+
|
|
391
218
|
module.exports = { Image }
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@live-change/image-service",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.36",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -23,7 +23,9 @@
|
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@live-change/framework": "0.6.5",
|
|
25
25
|
"@live-change/relations-plugin": "0.6.5",
|
|
26
|
-
"
|
|
26
|
+
"download": "^8.0.0",
|
|
27
|
+
"pluralize": "8.0.0",
|
|
28
|
+
"sharp": "^0.30.6"
|
|
27
29
|
},
|
|
28
|
-
"gitHead": "
|
|
30
|
+
"gitHead": "7175d4ca2f3308188b65f1093530091a2e8e0ca2"
|
|
29
31
|
}
|