refactor a bit

This commit is contained in:
Paul Fitzpatrick
2021-11-17 19:46:26 -05:00
parent cd69ec80a0
commit 6e36fafe6e
2 changed files with 194 additions and 23 deletions

176
index.js
View File

@@ -5,46 +5,176 @@ const childProcess = require('child_process')
const multer = require('multer')
const uuid = require('uuid')
const fs = require ('fs')
const app = express()
const os = require('os')
const path = require('path')
const workDir = '/tmp/api'
const port = 3000
const workDir = '/tmp/makesweet-api'
const upload = multer({ dest: workDir })
const app = express()
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.post('/make/:template', upload.any('images'), (req, res) => {
app.post('/make/:template', upload.any('images'), handleErrors, (req, res) => {
if (!req.files) {
return res.status(400).json({error: "i hunger for images..."})
throw new ApiError(400, 'i hunger for images...');
}
const template = `templates/${req.params.template}.zip`
if (!fs.existsSync(template)) {
return res.status(404).json({error: "i seek the template everywhere, but it is not to be found. try 'heart-locket'"})
}
const path=`${workDir}/${uuid.v4()}.gif`
const textHtml = `${process.cwd()}/text.html`;
const output = `${workDir}/${uuid.v4()}.gif`;
const template = getTemplatePathFromName(req.params.template);
const generator = new Generator();
generator.useTemplate(template);
try {
const cwd = process.cwd()
const command = ['run', '-v', `${workDir}:${workDir}`, '-v', `${cwd}:/share`, 'paulfitz/makesweet', '--zip', template, '--in']
for (const file of req.files) {
command.push(file.path)
}
command.push('--gif', path)
console.log(command)
const result = childProcess.execFileSync('docker', command).stdout
res.send(fs.readFileSync(path))
generator.addTexts(req.query.text, textHtml);
generator.addImages(req.files);
generator.setOutput(output);
res.send(generator.apply());
} finally {
for (const file of req.files) {
fs.unlinkSync(file.path)
}
fs.unlinkSync(path)
generator.clean();
}
})
app.use(handleErrors)
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
function execSync(command) {
console.log("Executing: " + command);
childProcess.execSync(command, { stdio: 'inherit' });
}
function text2image(htmlTemplate, outputDir, prefix, txt, border) {
console.log("text2image:", txt);
const data = {
text: txt.replace(/[ \n\r\t]+\/\/[ \n\r\t]+/g, '\n').trim()
};
fs.writeFileSync(`${outputDir}/text.json`, JSON.stringify(data));
fs.copyFileSync(htmlTemplate, `${outputDir}/text.html`);
console.log("ready");
execSync(`wkhtmltoimage --enable-local-file-access --transparent --width 3000 --window-status ready_to_print ${outputDir}/text.html ${outputDir}/${prefix}.png`);
console.log("123");
execSync(`convert ${outputDir}/${prefix}.png -trim -bordercolor none -border ` + border + ` ${outputDir}/${prefix}2.png`);
console.log("234");
return `${outputDir}/${prefix}2.png`;
}
function getTemplatePathFromName(name) {
if (!name) {
throw new ApiError(400, 'i must have a template i cannot do anything without a template');
}
const nameStr = String(name);
if (!nameStr.match(/^[-a-zA-Z0-9]+$/)) {
throw new ApiError(400, 'is that really a template: ' + nameStr);
}
const template = `templates/${nameStr}.zip`
if (!fs.existsSync(template)) {
throw new ApiError(404, 'i seek the template everywhere, but it is not to be found.');
}
return template;
}
function handleErrors(err, req, res, next) {
console.error(err);
if (err.code) {
return res.status(err.code).json({error: err.message})
} else {
res.status(500).json({error: err})
}
}
class ApiError extends Error {
constructor(code, message) {
super(message);
this.code = code;
}
}
class Generator {
constructor() {
this.fnames = [];
this.images = [];
this.texts = [];
this.template = null;
this.output = null;
this.tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'makesweet-api'));
}
useFile(fname) {
this.fnames.push(fname);
}
useTemplate(fname) {
this.useFile(fname);
this.template = fname;
}
addImages(files) {
if (!files) { return; }
for (const file of files) {
this.images.push(file.path);
this.fnames.push(file.path);
}
}
addTexts(textOrTexts, htmlTemplate) {
console.log({textOrTexts, htmlTemplate});
const texts = !textOrTexts ? [] :
(typeof textOrTexts === 'string') ? [textOrTexts] :
[...textOrTexts];
console.log("texts", texts)
let i = 0;
for (const txt of texts) {
const fname = text2image(htmlTemplate, this.tmpDir, `text_${i}`, txt, 60);
this.texts.push(fname);
this.fnames.push(fname);
i++;
}
}
setOutput(output) {
this.output = output;
this.fnames.push(output);
}
getVolumes() {
const dirs = new Set(this.fnames.map(fname => path.resolve(path.dirname(fname))));
return [...dirs].sort();
}
getCommand() {
const command = ['run'];
for (const volume of this.getVolumes()) {
command.push('-v', `${volume}:${volume}`);
}
command.push('--mount', 'type=tmpfs,destination=/share')
command.push('paulfitz/makesweet');
command.push('--zip', path.resolve(this.template));
command.push('--in');
for (const image of this.images) {
command.push(image);
}
for (const text of this.texts) {
command.push(text);
}
command.push('--gif', this.output);
return command;
}
apply() {
const command = this.getCommand();
console.log("command:", command);
childProcess.execFileSync('docker', command);
return fs.readFileSync(this.output);
}
clean() {
try {
fs.rmSync(this.tmpDir, { recursive: true });
for (const image of this.images) {
fs.unlinkSync(image);
}
if (this.output) {
fs.unlinkSync(this.output);
}
} catch (e) {
console.error(`Error removing tmpDir: ${e}`);
}
}
}

41
text.html Normal file
View File

@@ -0,0 +1,41 @@
<html>
<head>
<style>
body {
font-size: 240px;
}
#target {
text-align: center;
white-space: pre-line;
text-shadow: 0px 0px 8px #fff, 0px 0px 8px #fff, 0px 0px 8px #fff;
}
</style>
</head>
<body>
<p id="target">
This is a test<br>my friend.
</p>
<script>
function callback(txt) {
document.getElementById("target").innerHTML = JSON.parse(txt).text;
window.status = "ready_to_print";
}
function loadJSON() {
var xobj = new XMLHttpRequest();
xobj.overrideMimeType("application/json");
xobj.open('GET', './text.json', true);
xobj.onreadystatechange = function () {
if (xobj.readyState == 4) {
// Required use of an anonymous callback as .open will NOT return a value but simply returns undefined in asynchronous mode
callback(xobj.responseText);
}
};
xobj.send(null);
}
loadJSON();
</script>
</body>
</html>