Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,29 @@
# photo_collection
Backend For a Cloud Photo Storage app

Backend for a cloud photo storage app.

## Local Development

```bash
npm install
npm start
```

The server listens on `PORT` or `3030`.

## Configuration

The MySQL connection can be configured without editing source code:

```bash
MYSQL_HOST=127.0.0.1
MYSQL_USER=root
MYSQL_PASSWORD=
MYSQL_DATABASE=photo_collection
```

## Tests

```bash
npm test
```
29 changes: 23 additions & 6 deletions lib/fileserver.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,54 @@
exports.printcwd = function (ss='working') {
console.log(ss+" "+process.cwd());
};
exports.addUser = function(db, email, pass){
exports.addUser = function(db, email, pass, done){
db.query("insert into users (email, password) values (?,?)", [email, pass], function (err, row) {
if(done) return done(err, row);
if(err) throw err;
// console.log(row);
});
};
exports.listCollection = function(db, user_no, resp) {
db.query("select * from collections where user_no = ?", user_no, function (err, row) {
if(err) throw err;
if(err) {
resp.writeHead(500, {'Content-Type': 'application/json'});
resp.end(JSON.stringify({error: 'database_error'}));
return;
}
resp.end(JSON.stringify(row));
});
};
exports.listPhoto = function(db, collection_no, resp) {
db.query("select * from photographs where collection_number = ?", collection_no, function (err, row) {
if(err) throw err;
if(err) {
resp.writeHead(500, {'Content-Type': 'application/json'});
resp.end(JSON.stringify({error: 'database_error'}));
return;
}
// console.log(row);
resp.end(JSON.stringify(row));
});
};
exports.addPhoto = function(db, url, no, resp){
db.query("insert into photographs (photo_url, collection_number) values (?,?)", [url, no], function (err, row) {
if(err) throw err;
if(err) {
resp.writeHead(500, {'Content-Type': 'application/json'});
resp.end(JSON.stringify({error: 'database_error'}));
return;
}
// console.log(row);

exports.listPhoto(db, no, resp);
});
};
exports.login = function(db, param, resp) {
db.query("select * from users where email = ? and password = ?", [param.email, param.password], function (err, row) {
if(err) throw err;
if(err) {
resp.writeHead(500, {'Content-Type': 'application/json'});
resp.end(JSON.stringify({error: 'database_error'}));
return;
}
// console.log(row);
resp.end(JSON.stringify(row));
});
};
};
31 changes: 31 additions & 0 deletions lib/http.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
var qs = require('querystring');

exports.readFormBody = function(req, done) {
var body = '';
req.on('data', function(chunk) {
body += chunk;
if (body.length > 1024 * 1024) {
req.destroy();
done(new Error('request body too large'));
}
});
req.on('error', done);
req.on('end', function() {
try {
done(null, qs.parse(body));
} catch (err) {
done(err);
}
});
};

exports.sendJson = function(resp, statusCode, payload) {
resp.writeHead(statusCode, { 'Content-Type': 'application/json' });
resp.end(JSON.stringify(payload));
};

exports.requireFields = function(source, fields) {
return fields.filter(function(field) {
return source[field] == null || String(source[field]).trim() === '';
});
};
46 changes: 46 additions & 0 deletions lib/static-images.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
var fs = require('fs');
var path = require('path');

var IMAGE_ROOT = path.resolve(__dirname, '..', 'public', 'images');
var CONTENT_TYPES = {
'.gif': 'image/gif',
'.jpeg': 'image/jpeg',
'.jpg': 'image/jpeg',
'.png': 'image/png',
'.webp': 'image/webp'
};

function resolvePublicImagePath(requestPath) {
var relativePath = String(requestPath || '').replace(/^\/public\/images\/?/, '');
var absolutePath = path.resolve(IMAGE_ROOT, relativePath);
if (absolutePath.indexOf(IMAGE_ROOT + path.sep) !== 0 && absolutePath !== IMAGE_ROOT) {
return null;
}
return absolutePath;
}

function contentTypeFor(filePath) {
return CONTENT_TYPES[path.extname(filePath).toLowerCase()] || 'application/octet-stream';
}

exports.resolvePublicImagePath = resolvePublicImagePath;
exports.contentTypeFor = contentTypeFor;

exports.streamPublicImage = function(requestPath, resp) {
var absolutePath = resolvePublicImagePath(requestPath);
if (!absolutePath) {
resp.writeHead(400, { 'Content-Type': 'application/json' });
resp.end(JSON.stringify({ error: 'invalid_image_path' }));
return;
}

fs.stat(absolutePath, function(err, stats) {
if (err || !stats.isFile()) {
resp.writeHead(404, { 'Content-Type': 'application/json' });
resp.end(JSON.stringify({ error: 'image_not_found' }));
return;
}
resp.writeHead(200, { 'Content-Type': contentTypeFor(absolutePath) });
fs.createReadStream(absolutePath).pipe(resp);
});
};
37 changes: 37 additions & 0 deletions lib/uploads.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
var path = require('path');

var IMAGE_ROOT = path.resolve(__dirname, '..', 'public', 'images');
var SAFE_EXTENSIONS = {
'.gif': true,
'.jpeg': true,
'.jpg': true,
'.png': true,
'.webp': true
};

function sanitizeSegment(value) {
return String(value || '')
.replace(/[^a-zA-Z0-9_-]/g, '-')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '') || 'uncategorized';
}

function safeExtension(filename) {
var ext = path.extname(filename || '').toLowerCase();
return SAFE_EXTENSIONS[ext] ? ext : '.jpg';
}

exports.sanitizeSegment = sanitizeSegment;
exports.safeExtension = safeExtension;

exports.buildUploadTarget = function(collectionNumber, originalName) {
var collectionDir = sanitizeSegment(collectionNumber);
var filename = Date.now() + '-' + Math.random().toString(36).slice(2, 10) + safeExtension(originalName);
var directory = path.join(IMAGE_ROOT, collectionDir);
var absolutePath = path.join(directory, filename);
return {
directory: directory,
absolutePath: absolutePath,
publicPath: 'public/images/' + collectionDir + '/' + filename
};
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "1.0.0",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"test": "node --test",
"start": "node server.js"
},
"author": "",
Expand Down
Loading