Remove image actor service as not needed
This commit is contained in:
@@ -4,7 +4,6 @@
|
||||
server: 'dar-test-server',
|
||||
api: 'dar-test-api',
|
||||
email: 'dar-test-email',
|
||||
image: 'dar-test-image',
|
||||
},
|
||||
uri: {
|
||||
mongo: 'mongodb://localhost/dar-test-v1',
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
server: 'dar-server',
|
||||
api: 'dar-api',
|
||||
email: 'dar-email',
|
||||
image: 'dar-image',
|
||||
},
|
||||
uri: {
|
||||
mongo: 'mongodb://localhost/dar-v1',
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
"test": "jest",
|
||||
"actor:api": "monzilla 'src/api/**/*.js:src/database/**/*.js' -- babel-node src/api/index.js",
|
||||
"actor:api:debug": "babel-node --inspect-brk src/api/index.js",
|
||||
"actor:image": "monzilla 'src/image/**/*.js:src/(message-service|database)/**/*.js' -- babel-node src/image/index.js",
|
||||
"actor:image:debug": "babel-node --inspect-brk src/image/index.js",
|
||||
"actor:email": "monzilla 'src/email/**/*.js:src/(message-service|database)/**/*.js' -- babel-node src/email/index.js",
|
||||
"actor:email:debug": "babel-node --inspect-brk src/email/index.js"
|
||||
},
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import childProcess from 'child_process'
|
||||
import path from 'path'
|
||||
import timers from 'timers'
|
||||
import autobind from 'autobind-decorator'
|
||||
import childProcess from "child_process"
|
||||
import path from "path"
|
||||
import timers from "timers"
|
||||
import autobind from "autobind-decorator"
|
||||
|
||||
@autobind
|
||||
export class ServerTool {
|
||||
constructor(toolName, log) {
|
||||
this.toolName = toolName
|
||||
this.log = log
|
||||
this.actors = [
|
||||
{ name: 'api' },
|
||||
{ name: 'email' },
|
||||
{ name: 'image' },
|
||||
]
|
||||
this.actors = [{ name: "api" }, { name: "email" }]
|
||||
}
|
||||
|
||||
restart(actor) {
|
||||
@@ -27,7 +23,11 @@ export class ServerTool {
|
||||
const backOffTime = (Math.pow(2, Math.min(actor.backOff, 7)) - 1) * 1000
|
||||
|
||||
if (backOffTime > 0) {
|
||||
this.log.warn(`Actor '${actor.name}' died quickly, waiting ${Math.floor(backOffTime / 1000)} seconds to restart`)
|
||||
this.log.warn(
|
||||
`Actor '${actor.name}' died quickly, waiting ${Math.floor(
|
||||
backOffTime / 1000
|
||||
)} seconds to restart`
|
||||
)
|
||||
} else {
|
||||
this.log.warn(`Actor ${actor.name} died, restarting`)
|
||||
}
|
||||
@@ -36,9 +36,9 @@ export class ServerTool {
|
||||
actor.starts += 1
|
||||
actor.startTime = Date.now()
|
||||
actor.proc = childProcess.fork(actor.modulePath)
|
||||
actor.proc.on('exit', (code, signal) => {
|
||||
actor.proc.on("exit", (code, signal) => {
|
||||
// Don't restart if the exit was clean or Control+C
|
||||
if (code === 0 || signal === 'SIGINT') {
|
||||
if (code === 0 || signal === "SIGINT") {
|
||||
this.log.info(`Actor ${actor.name} terminated normally`)
|
||||
return
|
||||
}
|
||||
@@ -53,23 +53,30 @@ export class ServerTool {
|
||||
let promises = []
|
||||
|
||||
this.actors.forEach((actor) => {
|
||||
promises.push(new Promise((resolve, reject) => {
|
||||
promises.push(
|
||||
new Promise((resolve, reject) => {
|
||||
actor.modulePath = path.join(__dirname, actor.name)
|
||||
actor.startTime = Date.now()
|
||||
actor.backOff = 0
|
||||
actor.timeToInit = actor.timeToInit || 3000
|
||||
actor.proc = childProcess.fork(actor.modulePath)
|
||||
actor.proc.on('exit', (code, signal) => {
|
||||
actor.proc.on("exit", (code, signal) => {
|
||||
const timeSinceStart = Date.now() - actor.startTime
|
||||
|
||||
if (timeSinceStart < actor.timeToInit) {
|
||||
this.log.error(`Actor '${actor.name}' exited during initialization`)
|
||||
this.log.error(
|
||||
`Actor '${actor.name}' exited during initialization`
|
||||
)
|
||||
|
||||
if (this.actors) {
|
||||
this.actors.forEach((otherActor) => {
|
||||
if (otherActor !== actor) {
|
||||
this.log.info(`Terminating actor ${actor.name}, pid ${otherActor.proc.pid}`)
|
||||
otherActor.proc.kill('SIGINT')
|
||||
this.log.info(
|
||||
`Terminating actor ${actor.name}, pid ${
|
||||
otherActor.proc.pid
|
||||
}`
|
||||
)
|
||||
otherActor.proc.kill("SIGINT")
|
||||
otherActor.proc = null
|
||||
}
|
||||
})
|
||||
@@ -78,7 +85,9 @@ export class ServerTool {
|
||||
this.actors = null
|
||||
}
|
||||
|
||||
return reject(new Error('All actors must initialize on first start'))
|
||||
return reject(
|
||||
new Error("All actors must initialize on first start")
|
||||
)
|
||||
}
|
||||
|
||||
this.restart(actor)
|
||||
@@ -89,7 +98,8 @@ export class ServerTool {
|
||||
}
|
||||
}, actor.timeToInit)
|
||||
this.log.info(`Started actor '${actor.name}', pid ${actor.proc.pid}`)
|
||||
})) // new Promise()
|
||||
})
|
||||
) // new Promise()
|
||||
})
|
||||
|
||||
return Promise.all(promises)
|
||||
|
||||
@@ -29,7 +29,7 @@ class SendMessageTool {
|
||||
usage: ${this.toolName} [options] <file>
|
||||
|
||||
options:
|
||||
-x --exchange <exchange> Exchange to send the message too, e.g. tmr-image
|
||||
-x --exchange <exchange> Exchange to send the message too
|
||||
-t --type <message-type> The type of the message content
|
||||
`)
|
||||
return 0
|
||||
|
||||
@@ -22,7 +22,6 @@ export let userSchema = new Schema(
|
||||
unique: true,
|
||||
},
|
||||
newEmail: { type: String, match: regExpPattern.email },
|
||||
thumbnailImageId: { type: Schema.Types.ObjectId },
|
||||
emailToken: {
|
||||
type: {
|
||||
value: String,
|
||||
@@ -71,8 +70,6 @@ userSchema.methods.toClient = function(authUser) {
|
||||
_id: this._id,
|
||||
email: this.email,
|
||||
emailValidated: !!this.emailToken !== true,
|
||||
imageId: this.imageId,
|
||||
thumbnailImageId: this.thumbnailImageId,
|
||||
firstName: this.firstName,
|
||||
lastName: this.lastName,
|
||||
administrator: this.administrator,
|
||||
|
||||
@@ -1,298 +0,0 @@
|
||||
import Canvas from 'canvas'
|
||||
import fs from 'fs'
|
||||
import util from 'util'
|
||||
import createError from 'http-errors'
|
||||
import autobind from 'autobind-decorator'
|
||||
import stream from 'stream'
|
||||
|
||||
function streamToBuffer(readable) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var chunks = []
|
||||
var writeable = new stream.Writable()
|
||||
|
||||
writeable._write = function (chunk, enc, done) {
|
||||
chunks.push(chunk)
|
||||
done()
|
||||
}
|
||||
|
||||
readable.on('end', function () {
|
||||
resolve(Buffer.concat(chunks))
|
||||
})
|
||||
|
||||
readable.on('error', (err) => {
|
||||
reject(err)
|
||||
})
|
||||
|
||||
readable.pipe(writeable);
|
||||
})
|
||||
}
|
||||
|
||||
function pipeToGridFS(readable, gfsWriteable) {
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
readable.on('error', (error) => {
|
||||
reject(error)
|
||||
})
|
||||
gfsWriteable.on('error', (error) => {
|
||||
reject(error)
|
||||
})
|
||||
gfsWriteable.on('close', (file) => {
|
||||
resolve(file)
|
||||
})
|
||||
})
|
||||
readable.pipe(gfsWriteable)
|
||||
return promise
|
||||
}
|
||||
|
||||
function loadImage(buf) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const image = new Canvas.Image()
|
||||
|
||||
function cleanup () {
|
||||
image.onload = null
|
||||
image.onerror = null
|
||||
}
|
||||
|
||||
image.onload = () => {
|
||||
cleanup();
|
||||
resolve(image)
|
||||
}
|
||||
image.onerror = () => {
|
||||
cleanup();
|
||||
reject(new Error(`Failed to load the image "${buf}"`))
|
||||
}
|
||||
|
||||
image.src = buf
|
||||
})
|
||||
}
|
||||
|
||||
// Derived from https://stackoverflow.com/questions/20600800/js-client-side-exif-orientation-rotate-and-mirror-jpeg-images
|
||||
function getExifOrientation(buf) {
|
||||
if (buf.length < 2 || buf.readUInt16BE(0) != 0xFFD8) {
|
||||
return -2
|
||||
}
|
||||
|
||||
let length = buf.byteLength
|
||||
let offset = 2
|
||||
|
||||
while (offset < length) {
|
||||
let marker = buf.readUInt16BE(offset)
|
||||
offset += 2
|
||||
|
||||
if (marker == 0xFFE1) {
|
||||
if (buf.readUInt32BE(offset += 2) != 0x45786966) {
|
||||
return -1
|
||||
}
|
||||
let little = (buf.readUInt16BE(offset += 6) == 0x4949)
|
||||
offset += (little ? buf.readUInt32LE(offset + 4) : buf.readUInt32BE(offset + 4))
|
||||
|
||||
const numTags = (little ? buf.readUInt16LE(offset) : buf.readUInt16BE(offset))
|
||||
|
||||
offset += 2
|
||||
for (let i = 0; i < numTags; i++) {
|
||||
let val = (little ? buf.readUInt16LE(offset + (i * 12)) : buf.readUInt16BE(offset + (i * 12)))
|
||||
|
||||
if (val === 0x0112) {
|
||||
return (little ? buf.readUInt16LE(offset + (i * 12) + 8) : buf.readUInt16BE(offset + (i * 12) + 8))
|
||||
}
|
||||
}
|
||||
} else if ((marker & 0xFF00) != 0xFF00) {
|
||||
break
|
||||
} else {
|
||||
offset += buf.readUInt16BE(offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeOrientation(image, orientation) {
|
||||
let width = image.width
|
||||
let height = image.height
|
||||
let canvas = new Canvas(width, height)
|
||||
let ctx = canvas.getContext("2d")
|
||||
|
||||
if (4 < orientation && orientation < 9) {
|
||||
canvas.width = height
|
||||
canvas.height = width
|
||||
} else {
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
}
|
||||
|
||||
switch (orientation) {
|
||||
case 2: ctx.transform(-1, 0, 0, 1, width, 0); break
|
||||
case 3: ctx.transform(-1, 0, 0, -1, width, height ); break
|
||||
case 4: ctx.transform(1, 0, 0, -1, 0, height ); break
|
||||
case 5: ctx.transform(0, 1, 1, 0, 0, 0); break
|
||||
case 6: ctx.transform(0, 1, -1, 0, height , 0); break
|
||||
case 7: ctx.transform(0, -1, -1, 0, height , width); break
|
||||
case 8: ctx.transform(0, -1, 1, 0, 0, width); break
|
||||
default: return Promise.resolve(image)
|
||||
}
|
||||
|
||||
ctx.drawImage(image, 0, 0)
|
||||
return loadImage(canvas.toBuffer())
|
||||
}
|
||||
|
||||
@autobind
|
||||
export class ImageHandlers {
|
||||
constructor(container) {
|
||||
this.db = container.db
|
||||
this.log = container.log
|
||||
}
|
||||
|
||||
scaleImage(options) {
|
||||
const {
|
||||
newWidth = 400,
|
||||
newHeight = 100,
|
||||
scaleMode = 'scaleToFill',
|
||||
inputFile,
|
||||
inputAssetId,
|
||||
outputFile
|
||||
} = options
|
||||
|
||||
if (!inputFile && !inputAssetId) {
|
||||
return Promise.reject(createError.BadRequest(`No inputAssetId or inputFile given`))
|
||||
}
|
||||
|
||||
let canvas = new Canvas(newWidth, newHeight)
|
||||
let ctx = canvas.getContext("2d")
|
||||
|
||||
ctx.imageSmoothingEnabled = true
|
||||
|
||||
let loadPromise = null
|
||||
|
||||
if (inputFile) {
|
||||
loadPromise = util.promisify(fs.readFile)(inputFile)
|
||||
} else {
|
||||
loadPromise = streamToBuffer(this.db.gridfs.createReadStream({ _id: inputAssetId }))
|
||||
}
|
||||
|
||||
let orientation
|
||||
|
||||
return loadPromise.then((buf) => {
|
||||
orientation = getExifOrientation(buf)
|
||||
return loadImage(buf)
|
||||
}).then((img) => {
|
||||
return normalizeOrientation(img, orientation)
|
||||
}).then((img) => {
|
||||
let x = 0
|
||||
let y = 0
|
||||
let scale = 1
|
||||
|
||||
switch (scaleMode) {
|
||||
case 'aspectFill':
|
||||
if (img.width - newWidth > img.height - newHeight) {
|
||||
scale = newHeight / img.height
|
||||
x = -(img.width * scale - newWidth) / 2
|
||||
} else {
|
||||
scale = newWidth / img.width
|
||||
y = -(img.height * scale - newHeight) / 2
|
||||
}
|
||||
ctx.drawImage(img, x, y, img.width * scale, img.height * scale)
|
||||
break
|
||||
case 'aspectFit':
|
||||
if (img.width - newWidth > img.height - newHeight) {
|
||||
scale = newWidth / img.width
|
||||
y = (newHeight - img.height * scale) / 2
|
||||
} else {
|
||||
scale = newHeight / img.height
|
||||
x = (newWidth - img.width * scale) / 2
|
||||
}
|
||||
ctx.drawImage(img, x, y, img.width * scale, img.height * scale)
|
||||
break
|
||||
case 'scaleToFill':
|
||||
default:
|
||||
ctx.drawImage(img, 0, 0, newWidth, newHeight)
|
||||
break
|
||||
}
|
||||
|
||||
let savePromise = null
|
||||
let readable = canvas.createPNGStream()
|
||||
let writeable = null
|
||||
|
||||
if (outputFile) {
|
||||
writeable = fs.createWriteStream(outputFile)
|
||||
} else {
|
||||
const _id = this.db.newObjectId()
|
||||
writeable = this.db.gridfs.createWriteStream({
|
||||
_id,
|
||||
filename: _id + '.png',
|
||||
content_type: 'image/png',
|
||||
metadata: {
|
||||
scaledFrom: this.db.newObjectId(inputAssetId),
|
||||
width: newWidth,
|
||||
height: newHeight
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return pipeToGridFS(readable, writeable).then((file) => {
|
||||
let res = {}
|
||||
if (outputFile) {
|
||||
res.outputFile = outputFile
|
||||
} else if (file) {
|
||||
res.outputAssetId = file._id
|
||||
}
|
||||
return Promise.resolve(res)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
createPlaceholder(options) {
|
||||
const {
|
||||
width = 400,
|
||||
height = 100,
|
||||
fontSize = 36,
|
||||
fontName = 'helvetica neue, arial black, sans serif',
|
||||
fontWeight = 'bold',
|
||||
background = '#1B1B1B',
|
||||
foreground = '#333333',
|
||||
outputFile
|
||||
} = options
|
||||
|
||||
let canvas = new Canvas(width, height)
|
||||
let ctx = canvas.getContext("2d")
|
||||
const text = `${width}x${height}`
|
||||
|
||||
ctx.fillStyle = background
|
||||
ctx.fillRect(0, 0, width, height)
|
||||
|
||||
ctx.font = `${fontWeight} ${fontSize}px ${fontName}`
|
||||
ctx.fillStyle = foreground
|
||||
const tm = ctx.measureText(text)
|
||||
|
||||
if (tm.width <= width / 2 && fontSize <= height / 2) {
|
||||
ctx.textAlign = 'center'
|
||||
ctx.textBaseline = 'middle'
|
||||
ctx.fillText(text, width / 2, height / 2, width)
|
||||
}
|
||||
|
||||
let promise = null
|
||||
let readable = canvas.createPNGStream()
|
||||
let writeable = null
|
||||
|
||||
if (outputFile) {
|
||||
writeable = fs.createWriteStream(outputFile)
|
||||
} else {
|
||||
const _id = this.db.newObjectId()
|
||||
writeable = this.db.gridfs.createWriteStream({
|
||||
_id,
|
||||
filename: `${width}x${height}.png`,
|
||||
content_type: 'image/png',
|
||||
metadata: {
|
||||
width: width,
|
||||
height: height,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return pipeToGridFS(readable, writeable).then((file) => {
|
||||
let res = {}
|
||||
if (outputFile) {
|
||||
res.outputFile = outputFile
|
||||
} else if (file) {
|
||||
res.outputAssetId = file._id
|
||||
}
|
||||
return Promise.resolve(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import config from "config"
|
||||
import pino from "pino"
|
||||
import * as pinoExpress from "pino-pretty-express"
|
||||
import { DB } from "../database"
|
||||
import { MS } from "../message-service"
|
||||
import { ImageHandlers } from "./ImageHandlers"
|
||||
import path from "path"
|
||||
import fs from "fs"
|
||||
|
||||
const serviceName = config.get("serviceName.image")
|
||||
const isProduction = process.env.NODE_ENV === "production"
|
||||
let log = null
|
||||
|
||||
if (isProduction) {
|
||||
log = pino(
|
||||
{ name: serviceName },
|
||||
fs.createWriteStream(path.join(config.get("logDir"), serviceName + ".log"))
|
||||
)
|
||||
} else {
|
||||
const pretty = pinoExpress.pretty({})
|
||||
pretty.pipe(process.stdout)
|
||||
log = pino({ name: serviceName }, pretty)
|
||||
}
|
||||
|
||||
const ms = new MS(serviceName, { durable: false }, log)
|
||||
const db = new DB()
|
||||
let container = { db, ms, log }
|
||||
const mongoUri = config.get("uri.mongo")
|
||||
const amqpUri = config.get("uri.amqp")
|
||||
|
||||
Promise.all([db.connect(mongoUri), ms.connect(amqpUri)])
|
||||
.then(() => {
|
||||
log.info(`Connected to MongoDB at ${mongoUri}`)
|
||||
log.info(`Connected to RabbitMQ at ${amqpUri}`)
|
||||
|
||||
container = {
|
||||
...container,
|
||||
handlers: new ImageHandlers(container),
|
||||
}
|
||||
|
||||
ms.listen(container.handlers)
|
||||
})
|
||||
.catch((err) => {
|
||||
log.error(isProduction ? err.message : err)
|
||||
})
|
||||
Reference in New Issue
Block a user