Skip to content

Commit

Permalink
POC manipulation of uploaded images
Browse files Browse the repository at this point in the history
  • Loading branch information
joelpurra committed Jun 29, 2015
1 parent 1dc81f2 commit 18cb59b
Show file tree
Hide file tree
Showing 5 changed files with 305 additions and 56 deletions.
292 changes: 246 additions & 46 deletions app/web.js
Expand Up @@ -4,7 +4,9 @@ var configuration = require("configvention"),

AWS_ACCESS_KEY = configuration.get("AWS_ACCESS_KEY"),
AWS_SECRET_KEY = configuration.get("AWS_SECRET_KEY"),
AWS_REGION = configuration.get("AWS_REGION"),
S3_BUCKET = configuration.get("S3_BUCKET"),
BLITLINE_APP_ID = configuration.get("BLITLINE_APP_ID"),

bunyanConfig = {
name: "ftrf-web",
Expand All @@ -29,20 +31,21 @@ var configuration = require("configvention"),
stream: process.stdout
},
//
// {
// type: "rotating-file",
// // TODO: use path from configuration so that there's one log path per app.
// path: "ftrf.%NAME%.log",
// // Daily rotation.
// period: "1d",
// // Keep three files.
// count: 3,
// level: "warn",
// },
{
type: "rotating-file",
// TODO: use path from configuration so that there's one log path per app.
path: "ftrf-web.log",
// Daily rotation.
period: "1d",
// Keep three files.
count: 3,
level: "trace",
},
//
],
},
logger = require("bunyan").createLogger(bunyanConfig),
uuid = require("node-uuid"),

httpServerPort = configuration.get("PORT") || configuration.get("http-server-port"),
httpServerIp = configuration.get("http-server-ip"),
Expand Down Expand Up @@ -75,6 +78,10 @@ var configuration = require("configvention"),
return resolvePath.apply(null, parts);
},

startsWith = function(str, check) {
return str.substring(0, check.length) === check;
},

// Path to static resources like index.html, css etcetera
siteRootPath = resolvePathFromProjectRoot.apply(null, siteRootRelativePath.split("/")),

Expand All @@ -85,6 +92,7 @@ var configuration = require("configvention"),
}),

aws = require("aws-sdk"),
Blitline = require("simple_blitline_node"),

app = express();

Expand All @@ -99,6 +107,195 @@ app.use(helmet.hsts({

app.use(configuredHttpsRedirect());

aws.config.update({
accessKeyId: AWS_ACCESS_KEY,
secretAccessKey: AWS_SECRET_KEY
});
aws.config.update({
region: AWS_REGION,
signatureVersion: "v4"
});


function shortDateString(date) {
date = date || new Date();

var shortDate = date.toISOString().split("T")[0];

return shortDate;
}

function getBeforeKey(generatedId, extension) {
return "before/" + shortDateString() + "/" + new Date().valueOf() + "_" + generatedId + extension;
}

function getAfterKey(beforeKey) {
return beforeKey.replace(/^before\//, "after/");
}

function getS3UrlFromKey(key) {
// TODO: use a ready-made AWS S3 method instead.
if (startsWith(key, "/")) {
throw new Error("Key must not start with a slash.");
}
return "https://" + S3_BUCKET + ".s3." + AWS_REGION + ".amazonaws.com/" + key;
}

function blitlineCreateAddOverlayJob(beforeKey, afterKey, signedAfterUrl, clientFilename) {
if (!startsWith(beforeKey, "before/")) {
throw new Error("beforeKey must start with before/.");
}

if (!startsWith(afterKey, "after/")) {
throw new Error("afterKey must start with after/.");
}

logger.trace("Creating add overlay job", beforeKey, afterKey, signedAfterUrl, clientFilename);

var s3 = new aws.S3(),
blitline = new Blitline(),
job = {
"application_id": BLITLINE_APP_ID,
"src": getS3UrlFromKey(beforeKey),
"functions": [{
// TODO DEBUG REMOVE
"name": "gray_colorspace",
"params": {},

// "name": "composite",
// "params": {
// "src": "https://ftrf.example.com/resources/image/overlay/rainbow-flag-superwide.svg",
// "gravity": "CenterGravity",
// "scale_to_fit": {
// "height": "100%"
// },
// "src_percentage": 0.5,
// "dst_percentage": 0.5
// },
"save": {
"image_identifier": afterKey,
"s3_destination": {
"signed_url": signedAfterUrl,
"headers": {
"x-amz-acl": "public-read"
// TODO: save original client file name.
// "x-amz-meta-name": clientFilename
}
}
}
}]
};

blitline.addJob(job);

blitline.postJobs(function(response) {
logger.trace("Received add overlay job response", beforeKey, afterKey, signedAfterUrl, clientFilename, response);

//http://www.blitline.com/docs/postback#json
//
// {
// "results": {
// "images": [{
// "image_identifier": "MY_CLIENT_ID",
// "s3_url": "https://dev.blitline.s3.amazonaws.com/2011111513/1/fDIFJQVNlO6IeDZwXlruYg.jpg"
// }],
// "job_id": "4ec2e057c29aba53a5000001"
// }
// }
try {
if (response.results.failed_image_identifiers) {
// TODO: handle error.
logger.error("Blitline", "Failed image identifiers", beforeKey, afterKey, signedAfterUrl, clientFilename, response, response.results.failed_image_identifiers);
} else {
// TODO: let the client know?
logger.trace("Blitline", "Success", beforeKey, afterKey, signedAfterUrl, clientFilename, response, response.results, response.results.map(function(result) {
return result.images;
}), response.results.map(function(result) {
return result.images.map(function(image) {
return "'" + image.image_identifier + "' '" + image.s3_url + "'";
});
}));
}
} catch (e) {
logger.error("Blitline", "Catch", beforeKey, afterKey, clientFilename, response, e);
}
});
}

function getS3BitlineUrl(beforeKey, afterKey, clientFilename) {
if (!startsWith(beforeKey, "before/")) {
throw new Error("beforeKey must start with before/.");
}

if (!startsWith(afterKey, "after/")) {
throw new Error("afterKey must start with after/.");
}

logger.trace("Fetching S3 signed putObject url for Blitline", beforeKey, afterKey, clientFilename);

var s3 = new aws.S3(),
s3_params = {
Bucket: S3_BUCKET,
Key: afterKey,
Expires: 60,
ACL: "public-read",
// TODO: save original client file name.
// Metadata: metadata
};

s3.getSignedUrl("putObject", s3_params, function(err, signedAfterUrl) {
if (err) {
logger.error(err);
} else {
blitlineCreateAddOverlayJob(beforeKey, afterKey, signedAfterUrl, clientFilename);
}
});
}

function waitForClientS3Upload(beforeKey, afterKey, clientFilename) {
if (!startsWith(beforeKey, "before/")) {
throw new Error("beforeKey must start with before/.");
}

if (!startsWith(afterKey, "after/")) {
throw new Error("afterKey must start with after/.");
}

logger.trace("Waiting for file upload", beforeKey, afterKey, clientFilename);

var s3 = new aws.S3(),
s3_params = {
Bucket: S3_BUCKET,
Key: afterKey,
},
objectExistsCallback = function(err, metadata) {
if (err && err.code === "Not Found") {
// TODO: What, it doesn"t exist? Hmm. Check metadata?
logger.error("Object didn't exist", beforeKey, afterKey, err, metadata)
} else {
logger.trace("Object exists", beforeKey, afterKey, metadata);

getS3BitlineUrl(beforeKey, afterKey, clientFilename);
}
};

// TODO: Give the client browser a second to receive this reply and upload the file to S3 before initial check?
s3.waitFor("objectExists", s3_params, objectExistsCallback);
}

function getExtensionFromInternetMediaType(internetMediaType) {
switch (internetMediaType) {
case "image/jpeg":
return ".jpg";
case "image/png":
return ".png";
default:
break;
}

return null;
}

(function() {
// Based on https://github.com/flyingsparx/NodeDirectUploader
// Apache 2.0 license.
Expand All @@ -111,49 +308,53 @@ app.use(configuredHttpsRedirect());
* anticipated URL of the image.
*/
app.get("/sign_s3", function(req, res) {
aws.config.update({
accessKeyId: AWS_ACCESS_KEY,
secretAccessKey: AWS_SECRET_KEY
});
aws.config.update({
region: "eu-central-1",
signatureVersion: "v4"
});
var s3 = new aws.S3();
var s3_params = {
Bucket: S3_BUCKET,
Key: req.query.file_name,
Expires: 60,
ContentType: req.query.file_type,
ACL: "public-read"
};
s3.getSignedUrl("putObject", s3_params, function(err, data) {
// TODO: better verification.
// TODO: check which types blitline can handle.
if (req.query.file_type !== "image/jpeg" && req.query.file_type !== "image/png") {
res.status(415); // 415 Unsupported Media Type
res.end();
return;
}

var s3 = new aws.S3(),
clientFilename = (req.query.file_name || ""),
imageContentType = req.query.file_type,
extension = getExtensionFromInternetMediaType(imageContentType),
generatedId = uuid.v4(),
beforeKey = getBeforeKey(generatedId, extension),
afterKey = getAfterKey(beforeKey),
beforeUrl = getS3UrlFromKey(beforeKey),
afterUrl = getS3UrlFromKey(afterKey),
// TODO: save original client file name.
// metadata = {
// name: clientFilename
// },
s3_params = {
Bucket: S3_BUCKET,
Key: beforeKey,
Expires: 60,
ContentType: imageContentType,
ACL: "public-read",
// TODO: save original client file name.
// Metadata: metadata
};

s3.getSignedUrl("putObject", s3_params, function(err, signedBeforeUrl) {
if (err) {
logger.error(err);
} else {
var generatedFilename = (new Date().valueOf() + req.query.file_name),
return_data = {
signed_request: data,
url: "https://" + S3_BUCKET + ".s3.amazonaws.com/" + "before/" + generatedFilename
};
var return_data = {
signed_request: signedBeforeUrl,
beforeUrl: beforeUrl,
afterUrl: afterUrl
};
res.write(JSON.stringify(return_data));
res.end();

waitForClientS3Upload(beforeKey, afterKey, clientFilename);
}
});
});

/*
* Respond to POST requests to /submit_form.
* This function needs to be completed to handle the information in
* a way that suits your application.
*/
app.post("/submit_form", function(req, res) {
username = req.body.username;
full_name = req.body.full_name;
avatar_url = req.body.avatar_url;
update_account(username, full_name, avatar_url); // TODO: create this function
// TODO: Return something useful or redirect
});
}());

app.use(mount);
Expand All @@ -162,6 +363,5 @@ app.listen(httpServerPort, httpServerIp, function() {
logger.info("Listening on port", httpServerPort);
logger.info("Bound to interface with ip", httpServerIp);
logger.info("Serving site root from folder", siteRootPath);
logger.info("Using AWS_ACCESS_KEY", AWS_ACCESS_KEY);
logger.info("Using S3_BUCKET", S3_BUCKET);
});
2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -14,6 +14,8 @@
"express": "^4.12.4",
"helmet": "^0.6.0",
"morgan": "^1.5.3",
"node-uuid": "^1.4.3",
"simple_blitline_node": "^2.2.0",
"st": "^0.4.1"
},
"devDependencies": {
Expand Down
14 changes: 10 additions & 4 deletions public/index.html
Expand Up @@ -46,20 +46,26 @@ <h2>
Heading
<small>Subheading</small>
</h2>
<fieldset>
<legend>Select an image</legend>
<input type="file" accept="image/*" id="file_input" />
</fieldset>
<div class="two columns">
<div class="left column">
<h3>
Heading
</h3>
<fieldset>
<legend>Select an image</legend>
<input type="file" accept="image/*" id="file_input" />
</fieldset>
<p>
<img id="source-image" src="/resources/image/icon-256x256.png" />
</p>
</div>
<div class="right column">
<h3>
Heading
</h3>
<p>
<img id="resulting-image" src="/resources/image/icon-256x256.png" />
</p>
</div>
</div>
</section>
Expand Down

0 comments on commit 18cb59b

Please sign in to comment.