Started adding maintainer modes for managing duplicates, deletions, trash, etc.

Signed-off-by: James Ketrenos <james_git@ketrenos.com>
This commit is contained in:
James Ketr 2018-10-06 16:00:02 -07:00
parent a930d27860
commit 064e228226
7 changed files with 302 additions and 155 deletions

View File

@ -46,7 +46,7 @@
</style> </style>
<div class="layout vertical start"> <div class="layout vertical start">
<div>[[item.name]]</div> <div>[[item.name]] ([[item.id]])</div>
<div>[[item.taken]]</div> <div>[[item.taken]]</div>
<div on-tap="_pathTap">[[item.path]]</div> <div on-tap="_pathTap">[[item.path]]</div>
</div> </div>

View File

@ -310,6 +310,8 @@
<!--paper-tab tab="time"><paper-icon-button icon="date-range"></paper-icon-button></paper-tab--> <!--paper-tab tab="time"><paper-icon-button icon="date-range"></paper-icon-button></paper-tab-->
<paper-tab tab="memories"><paper-icon-button icon="today"></paper-icon-button></paper-tab> <paper-tab tab="memories"><paper-icon-button icon="today"></paper-icon-button></paper-tab>
<paper-tab tab="albums"><paper-icon-button icon="folder"></paper-icon-button></paper-tab> <paper-tab tab="albums"><paper-icon-button icon="folder"></paper-icon-button></paper-tab>
<paper-tab hidden$="[[!user.maintainer]]" tab="duplicates"><paper-icon-button icon="compare-arrows"></paper-icon-button></paper-tab>
<paper-tab hidden$="[[!user.maintainer]]" tab="trash"><paper-icon-button icon="delete"></paper-icon-button></paper-tab>
</paper-tabs> </paper-tabs>
<iron-pages id="pages" attr-for-selected="id" selected="[[mode]]" fallback-selection="loading"> <iron-pages id="pages" attr-for-selected="id" selected="[[mode]]" fallback-selection="loading">
<div id="loading"></div> <div id="loading"></div>
@ -336,6 +338,21 @@
<div>On <b>[[memoryDate]]</b>, there have been <b>[[add(thumbnails.length,pendingPhotos.length)]]</b> photos taken over <b>[[years.length]]</b> year(s).</div> <div>On <b>[[memoryDate]]</b>, there have been <b>[[add(thumbnails.length,pendingPhotos.length)]]</b> photos taken over <b>[[years.length]]</b> year(s).</div>
</div> </div>
</div> </div>
<div hidden$="[[!user.maintainer]]" id="trash" class="flex layout vertical">
<div><b>Trash</b></div>
<div>There are <b>[[add(thumbnails.length,pendingPhotos.length)]]</b> photos in the trash.</div>
<div>Do you want to purge the trash?</div>
</div>
<div hidden$="[[!user.maintainer]]" id="duplicates" class="flex layout vertical">
<div><b>Duplicate names</b></div>
<div>There are <b>[[add(thumbnails.length,pendingPhotos.length)]]</b> photos which may be duplicates
based on either their name.</div>
<div>Look for duplicates in each file-name pair. If they are not the same,
tap <iron-icon icon="text-format"></iron-icon> on the photo and that one will be renamed
on the server by adding four letters from the image's signature to the name.</div>
<div>If they are duplicates, you can tap <iron-icon icon="delete"></iron-icon> to move the
photo to the trash.</div>
</div>
<div id="albums" class="flex layout vertical"> <div id="albums" class="flex layout vertical">
<template is="dom-repeat" items="[[breadcrumb(path)]]"> <template is="dom-repeat" items="[[breadcrumb(path)]]">
<div tabindex="0" on-tap="loadPath">[[item.name]] /</div> <div tabindex="0" on-tap="loadPath">[[item.name]] /</div>
@ -468,10 +485,6 @@
type: Object, type: Object,
value: null value: null
}, },
order: {
type: String,
value: "by-date"
},
loading: Boolean, loading: Boolean,
pendingPhotos: { pendingPhotos: {
type: Array, type: Array,
@ -486,10 +499,6 @@
value: true, value: true,
reflectToAttribute: true reflectToAttribute: true
}, },
showAlbums: {
type: Boolean,
computed: "shouldShowAlbums(order)"
},
path: { path: {
type: String, type: String,
value: "" value: ""
@ -575,10 +584,6 @@
this.date = "2016-" + window.moment(Math.ceil(Math.random() * 365), "DDD").format("MM-DD"); this.date = "2016-" + window.moment(Math.ceil(Math.random() * 365), "DDD").format("MM-DD");
}, },
shouldShowAlbums: function(order) {
return order == "by-album";
},
login: function(event) { login: function(event) {
if (this.loading) { if (this.loading) {
return; return;
@ -787,7 +792,7 @@
} }
} }
if (top) { if (top && (this.mode == "memories" || this.mode == "albums")) {
var photo = top.item; var photo = top.item;
this.$.pager.style.opacity = 1; this.$.pager.style.opacity = 1;
var date = window.moment(new Date((photo.taken || photo.modified || photo.added).replace(/T.*/, " GMT"))); var date = window.moment(new Date((photo.taken || photo.modified || photo.added).replace(/T.*/, " GMT")));
@ -1091,58 +1096,117 @@
throw error; throw error;
} }
var dateBlock = this.root.querySelector("#date-" + datetime), thumbnails; if (this.mode == "duplicates") {
if (!dateBlock) { var name = photo.filename.replace(/\./g, "_"),
dateBlock = document.createElement("div"); nameBlock = this.root.querySelector("#name-" + name), thumbnails;
dateBlock.id = "date-" + datetime; if (!nameBlock) {
dateBlock.classList.add("date-line"); nameBlock = document.createElement("div");
dateBlock.classList.add("layout"); nameBlock.id = "name-" + name;
dateBlock.classList.add("vertical"); nameBlock.classList.add("date-line");
var header = document.createElement("div"); nameBlock.classList.add("layout");
header.classList.add("header"); nameBlock.classList.add("vertical");
header.classList.add("layout"); var header = document.createElement("div");
header.classList.add("center"); header.classList.add("header");
header.classList.add("horizontal"); header.classList.add("layout");
var div = document.createElement("div"); header.classList.add("center");
div.classList.add("date"); header.classList.add("horizontal");
if (this.mode == "memories") { var div = document.createElement("div");
var ago = window.moment(datetime, "YYYY-MM-DD").fromNow(); div.classList.add("date");
ago = ago.charAt(0).toUpperCase() + ago.substr(1); div.textContent = photo.filename;
div.innerHTML = "<b>" + ago + "</b><br><span style='font-size: 0.8em;font-weight: normal'>" + window.moment(datetime, "YYYY-MM-DD").calendar(null, { sameElse: "MMM DD, YYYY" }).replace(/ at.*/, "") + "</span>"; Polymer.dom(nameBlock).appendChild(header);
Polymer.dom(header).appendChild(div);
thumbnails = document.createElement("div");
thumbnails.classList.add("thumbnails");
thumbnails.classList.add("layout");
thumbnails.classList.add("horizontal");
thumbnails.classList.add("wrap");
Polymer.dom(nameBlock).appendChild(thumbnails);
Polymer.dom(this.$.thumbnails).appendChild(nameBlock);
} else { } else {
div.textContent = window.moment(datetime, "YYYY-MM-DD").calendar(null, { sameElse: "MMM DD, YYYY" }).replace(/ at.*/, ""); thumbnails = Polymer.dom(nameBlock).querySelector(".thumbnails");
}
} else if (this.mode == "trash") {
var trashBlock = this.root.querySelector("#trash-images"), thumbnails;
if (!trashBlock) {
trashBlock = document.createElement("div");
trashBlock.id = "trash-images";
trashBlock.classList.add("date-line");
trashBlock.classList.add("layout");
trashBlock.classList.add("vertical");
var header = document.createElement("div");
header.classList.add("header");
header.classList.add("layout");
header.classList.add("center");
header.classList.add("horizontal");
var div = document.createElement("div");
div.classList.add("date");
div.textContent = "Trash";
Polymer.dom(trashBlock).appendChild(header);
Polymer.dom(header).appendChild(div);
thumbnails = document.createElement("div");
thumbnails.classList.add("thumbnails");
thumbnails.classList.add("layout");
thumbnails.classList.add("horizontal");
thumbnails.classList.add("wrap");
Polymer.dom(trashBlock).appendChild(thumbnails);
Polymer.dom(this.$.thumbnails).appendChild(trashBlock);
} else {
thumbnails = Polymer.dom(trashBlock).querySelector(".thumbnails");
} }
Polymer.dom(dateBlock).appendChild(header);
Polymer.dom(header).appendChild(div);
thumbnails = document.createElement("div");
thumbnails.classList.add("thumbnails");
thumbnails.classList.add("layout");
thumbnails.classList.add("horizontal");
thumbnails.classList.add("wrap");
Polymer.dom(dateBlock).appendChild(thumbnails);
Polymer.dom(this.$.thumbnails).appendChild(dateBlock);
} else { } else {
thumbnails = Polymer.dom(dateBlock).querySelector(".thumbnails"); var dateBlock = this.root.querySelector("#date-" + datetime), thumbnails;
} if (!dateBlock) {
dateBlock = document.createElement("div");
dateBlock.id = "date-" + datetime;
dateBlock.classList.add("date-line");
dateBlock.classList.add("layout");
dateBlock.classList.add("vertical");
var header = document.createElement("div");
header.classList.add("header");
header.classList.add("layout");
header.classList.add("center");
header.classList.add("horizontal");
var div = document.createElement("div");
div.classList.add("date");
if (this.mode == "memories") {
var ago = window.moment(datetime, "YYYY-MM-DD").fromNow();
ago = ago.charAt(0).toUpperCase() + ago.substr(1);
div.innerHTML = "<b>" + ago + "</b><br><span style='font-size: 0.8em;font-weight: normal'>" + window.moment(datetime, "YYYY-MM-DD").calendar(null, { sameElse: "MMM DD, YYYY" }).replace(/ at.*/, "") + "</span>";
} else {
div.textContent = window.moment(datetime, "YYYY-MM-DD").calendar(null, { sameElse: "MMM DD, YYYY" }).replace(/ at.*/, "");
}
Polymer.dom(dateBlock).appendChild(header);
Polymer.dom(header).appendChild(div);
thumbnails = document.createElement("div");
thumbnails.classList.add("thumbnails");
thumbnails.classList.add("layout");
thumbnails.classList.add("horizontal");
thumbnails.classList.add("wrap");
Polymer.dom(dateBlock).appendChild(thumbnails);
Polymer.dom(this.$.thumbnails).appendChild(dateBlock);
} else {
thumbnails = Polymer.dom(dateBlock).querySelector(".thumbnails");
}
if (this.order == "by-album") { if (this.mode == "albums") {
if (lastPath != photo.path) { if (lastPath != photo.path) {
lastPath = photo.path; lastPath = photo.path;
var albumBlock = document.createElement("div"); var albumBlock = document.createElement("div");
albumBlock.classList.add("album-line"); albumBlock.classList.add("album-line");
albumBlock.classList.add("layout"); albumBlock.classList.add("layout");
albumBlock.classList.add("horizontal"); albumBlock.classList.add("horizontal");
var trail = this.breadcrumb(lastPath); var trail = this.breadcrumb(lastPath);
trail.forEach(function(crumb) { trail.forEach(function(crumb) {
var div = document.createElement("div"); var div = document.createElement("div");
div.path = crumb.path; div.path = crumb.path;
div.textContent = crumb.name + " /"; div.textContent = crumb.name + " /";
div.addEventListener("tap", this.pathTapped.bind(this)); div.addEventListener("tap", this.pathTapped.bind(this));
albumBlock.appendChild(div); albumBlock.appendChild(div);
}.bind(this)); }.bind(this));
var header = dateBlock.querySelector(".header"); var header = dateBlock.querySelector(".header");
Polymer.dom(header).appendChild(albumBlock); Polymer.dom(header).appendChild(albumBlock);
}
} }
} }
@ -1188,9 +1252,6 @@
if (start) { if (start) {
params.next = start; params.next = start;
} }
if (this.sortOrder) {
params.sort = this.sortOrder;
}
for (var key in params) { for (var key in params) {
if (query == "") { if (query == "") {
query = "?"; query = "?";
@ -1205,7 +1266,7 @@
path = mode; path = mode;
if (mode == "time") { if (mode == "time") {
path = ""; path = "";
} else { } else if (mode == "memories") {
path = "memories/" + (this.date.replace(/2016-/, "") || ""); path = "memories/" + (this.date.replace(/2016-/, "") || "");
} }
} }
@ -1323,6 +1384,7 @@
}, },
userChanged: function(user) { userChanged: function(user) {
console.log("User: ", user);
if (!this.firstRequest) { if (!this.firstRequest) {
this.mode = "loading"; this.mode = "loading";
return; return;
@ -1394,6 +1456,7 @@
this.$.toast.setAttribute("error", true); this.$.toast.setAttribute("error", true);
this.$.toast.updateStyles(); this.$.toast.updateStyles();
this.$.toast.show(); this.$.toast.show();
console.log(xhr.responseText);
return; return;
} }

View File

@ -28,7 +28,7 @@ config.get("smtp.sender");
let basePath = config.get("basePath"); let basePath = config.get("basePath");
let photoDB = null, userDB = null let photoDB = null, userDB = null;
basePath = "/" + basePath.replace(/^\/+/, "").replace(/\/+$/, "") + "/"; basePath = "/" + basePath.replace(/^\/+/, "").replace(/\/+$/, "") + "/";
if (basePath == "//") { if (basePath == "//") {
@ -53,6 +53,26 @@ app.use(bodyParser.urlencoded({
extended: false extended: false
})); }));
/* ******************************************************************************* */
/* Logging */
/* This runs before after cookie parsing, but before routes. If we set
* immediate: true on the morgan options, it happens before cookie parsing */
morgan.token('remote-user', function (req) {
return req.user ? req.user.username : "N/A";
});
const logSkipPaths = new RegExp("^" + basePath + "(" + [
"bower_components",
].join(")|(") + ")");
console.log(logSkipPaths);
app.use(morgan('common', {
skip: function (req) {
return logSkipPaths.exec(req.originalUrl);
}
}));
/* Logging */
/* ******************************************************************************* */
/* body-parser does not support text/*, so add support for that here */ /* body-parser does not support text/*, so add support for that here */
app.use(function(req, res, next){ app.use(function(req, res, next){
if (!req.is('text/*')) { if (!req.is('text/*')) {
@ -110,7 +130,8 @@ const templates = {
].join("\n") ].join("\n")
}; };
/* Allow loading of the app w/out being logged in */ /* Look for action-token URLs and process; this does not require a user to be logged
* in */
app.use(basePath, function(req, res, next) { app.use(basePath, function(req, res, next) {
let match = req.url.match(/^\/([0-9a-f]+)$/); let match = req.url.match(/^\/([0-9a-f]+)$/);
if (!match) { if (!match) {
@ -197,9 +218,12 @@ app.use(basePath, function(req, res, next) {
}); });
}); });
/* Allow loading of the app w/out being logged in */
app.use(basePath, index); app.use(basePath, index);
app.use(basePath + "api/v1/users", require("./routes/users")); /* Allow access to the 'users' API w/out being logged in */
const users = require("./routes/users");
app.use(basePath + "api/v1/users", users.router);
app.use(function(err, req, res, next) { app.use(function(err, req, res, next) {
res.status(err.status || 500).json({ res.status(err.status || 500).json({
@ -208,68 +232,19 @@ app.use(function(err, req, res, next) {
}); });
}); });
/* Everything below here requires a successful authentication */ /* Check authentication */
app.use(basePath, function(req, res, next) { app.use(basePath, function(req, res, next) {
if (!req.session || !req.session.userId) { return users.getSessionUser(req).then(function(user) {
return res.status(401).send("Unauthorized"); req.user = user;
} return next();
}).catch(function(error) {
if (req.session.userId == "LDAP") { return res.status(401).send(error);
if (req.session.ldapUser) {
req.user = req.session.ldapUser;
return next();
}
req.session.userId = null;
req.session.ldapUser = null;
return res.status(401).send("Invalid LDAP session");
}
let query = "SELECT uid AS username,displayName,mailVerified,authenticated,memberSince AS name,mail " +
"FROM users WHERE id=:id";
return userDB.sequelize.query(query, {
replacements: {
id: req.session.userId
},
type: userDB.Sequelize.QueryTypes.SELECT,
raw: true
}).then(function(results) {
if (results.length != 1) {
return res.status(401).send("Invalid account");
}
req.user = results[0];
if (!req.user.authenticated) {
return res.status(401).send("Accout not authenticated.");
}
if (!req.user.mailVerified) {
return res.status(401).send("Account mail not verified.");
}
if (!config.has("restrictions")) {
return next();
}
let allowed = config.get("restrictions");
if (!Array.isArray(allowed)) {
allowed = [ allowed ];
}
for (let i = 0; i < allowed.length; i++) {
if (allowed[i] == req.user.username) {
return next();
}
}
console.log("Unauthorized (logged in) access by user: " + req.user.username);
return res.status(401).send("Unauthorized");
}); });
}); });
/* Everything below here requires a successful authentication */
app.use(basePath, express.static(picturesPath, { index: false })); app.use(basePath, express.static(picturesPath, { index: false }));
app.use(morgan("common"));
app.use(basePath + "api/v1/photos", require("./routes/photos")); app.use(basePath + "api/v1/photos", require("./routes/photos"));
app.use(basePath + "api/v1/days", require("./routes/days")); app.use(basePath + "api/v1/days", require("./routes/days"));
app.use(basePath + "api/v1/albums", require("./routes/albums")); app.use(basePath + "api/v1/albums", require("./routes/albums"));

View File

@ -60,6 +60,7 @@ function init() {
taken: Sequelize.DATE, taken: Sequelize.DATE,
width: Sequelize.INTEGER, width: Sequelize.INTEGER,
height: Sequelize.INTEGER, height: Sequelize.INTEGER,
size: Sequelize.INTEGER,
duplicate: { duplicate: {
type: Sequelize.BOOLEAN, type: Sequelize.BOOLEAN,
defaultValue: 0 defaultValue: 0

View File

@ -23,7 +23,6 @@ const router = express.Router();
* photo info * photo info
*/ */
router.get("/memories/:date?", function(req, res/*, next*/) { router.get("/memories/:date?", function(req, res/*, next*/) {
let limit = parseInt(req.query.limit) || 50, let limit = parseInt(req.query.limit) || 50,
id, cursor, index; id, cursor, index;
@ -96,6 +95,54 @@ router.get("/memories/:date?", function(req, res/*, next*/) {
}); });
}); });
router.get("/duplicates", function(req, res/*, next*/) {
let replacements = {};
return photoDB.sequelize.query(
"SELECT filename,COUNT(*) AS count FROM photos WHERE photos.duplicate!=1 AND photos.deleted!=1 GROUP BY filename HAVING count > 1", {
replacements: replacements,
type: photoDB.Sequelize.QueryTypes.SELECT,
raw: true
}).then(function(duplicates) {
let filenames = [];
duplicates.forEach(function(duplicate) {
filenames.push(duplicate.filename);
});
replacements.filenames = filenames;
return photoDB.sequelize.query(
"SELECT photos.*,albums.path AS path,photohashes.hash,(albums.path || photos.filename) AS filepath FROM photos " +
"LEFT JOIN albums ON albums.id=photos.albumId " +
"LEFT JOIN photohashes ON photohashes.photoId=photos.id " +
"WHERE filename IN (:filenames) ORDER BY photos.filename", {
replacements: replacements,
type: photoDB.Sequelize.QueryTypes.SELECT,
raw: true
}).then(function(photos) {
return res.status(200).json({
items: photos
});
});
}).catch(function(error) {
return Promise.reject(error);
});
});
router.get("/trash", function(req, res/*, next*/) {
return photoDB.sequelize.query(
"SELECT photos.*,albums.path AS path,(albums.path || photos.filename) AS filepath FROM photos " +
"LEFT JOIN albums ON albums.id=photos.albumId " +
"WHERE deleted=1 ORDER BY photos.filename", {
type: photoDB.Sequelize.QueryTypes.SELECT,
raw: true
}).then(function(photos) {
return res.status(200).json({
items: photos
});
});
});
router.get("/*", function(req, res/*, next*/) { router.get("/*", function(req, res/*, next*/) {
let limit = parseInt(req.query.limit) || 50, let limit = parseInt(req.query.limit) || 50,
id, cursor, index; id, cursor, index;
@ -167,4 +214,5 @@ router.get("/*", function(req, res/*, next*/) {
}); });
}); });
module.exports = router; module.exports = router;

View File

@ -31,8 +31,10 @@ require("../db/users").then(function(db) {
router.get("/", function(req, res/*, next*/) { router.get("/", function(req, res/*, next*/) {
console.log("/users/"); console.log("/users/");
return getSessionUser(req).then(function(user) { return getSessionUser(req).then(function(user) {
req.user = user; return res.status(200).send(user);
return res.status(200).send(req.user); }).catch(function(error) {
console.log("User not logged in: " + error);
return res.status(200).send({});
}); });
}); });
@ -185,33 +187,87 @@ router.post("/create", function(req, res) {
return res.status(200).send(user); return res.status(200).send(user);
}); });
}); });
}).catch(function(error) {
console.log("Error creating account: ", error);
return res.status(401).send(error);
}); });
}); });
const getSessionUser = function(req) { const getSessionUser = function(req) {
if (!req.session.userId) { return Promise.resolve().then(function() {
return Promise.resolve({}); if (!req.session || !req.session.userId) {
} throw "Unauthorized. You must be logged in.";
if (req.session.userId == "LDAP") {
return Promise.resolve(req.session.ldapUser);
}
let query = "SELECT " +
"uid AS username,displayName,mailVerified,authenticated,memberSince AS name,mail " +
"FROM users WHERE id=:id";
return userDB.sequelize.query(query, {
replacements: {
id: req.session.userId
},
type: userDB.Sequelize.QueryTypes.SELECT,
raw: true
}).then(function(results) {
if (results.length != 1) {
return {};
} }
return results[0]; if (req.session.userId == "LDAP") {
if (req.session.ldapUser) {
return req.session.ldapUser;
}
req.session.userId = null;
req.session.ldapUser = null;
throw "Invalid LDAP session";
}
let query = "SELECT " +
"uid AS username,displayName,mailVerified,authenticated,memberSince AS name,mail " +
"FROM users WHERE id=:id";
return userDB.sequelize.query(query, {
replacements: {
id: req.session.userId
},
type: userDB.Sequelize.QueryTypes.SELECT,
raw: true
}).then(function(results) {
if (results.length != 1) {
throw "Invalid account.";
}
req.user = results[0];
if (!req.user.authenticated) {
throw "Accout not authenticated.";
}
if (!req.user.mailVerified) {
throw "Account mail not verified.";
}
});
}).then(function(user) {
if (!config.has("restrictions")) {
return user;
}
let allowed = config.get("restrictions");
if (!Array.isArray(allowed)) {
allowed = [ allowed ];
}
for (let i = 0; i < allowed.length; i++) {
if (allowed[i] == user.username) {
return user;
}
}
console.log("Unauthorized (logged in) access by user: " + req.user.username);
throw "Unauthorized access attempt to restricted album.";
}).then(function(user) {
if (config.has("maintainers")) {
let maintainers = config.get("maintainers");
if (maintainers.indexOf(user.username) != -1) {
user.maintainer = true;
}
}
return user;
}).then(function(user) {
/* Strip out any fields that shouldn't be there. The allowed fields are: */
let allowed = [
"maintainer", "username", "displayName", "mailVerified", "authenticated", "name", "mail"
];
for (let field in user) {
if (allowed.indexOf(field) == -1) {
delete user[field];
}
}
return user;
}); });
} }
@ -293,4 +349,7 @@ router.get("/logout", function(req, res) {
res.status(200).send({}); res.status(200).send({});
}); });
module.exports = router; module.exports = {
router,
getSessionUser
};

View File

@ -198,7 +198,7 @@ function processBlock(items) {
/* Sort to newest files to be processed first */ /* Sort to newest files to be processed first */
processing.sort(function(a, b) { processing.sort(function(a, b) {
return a.stats.mtime - b.stats.mtime; return b.stats.mtime - a.stats.mtime;
}); });
let toProcess = processing.length, lastMessage = moment(); let toProcess = processing.length, lastMessage = moment();
@ -349,7 +349,7 @@ function processBlock(items) {
}); });
}).then(function() { }).then(function() {
return photoDB.sequelize.query("UPDATE photos SET " + return photoDB.sequelize.query("UPDATE photos SET " +
"added=:added,modified=:modified,taken=:taken,width=:width,height=:height,scanned=CURRENT_TIMESTAMP " + "added=:added,modified=:modified,taken=:taken,width=:width,height=:height,size=:size,scanned=CURRENT_TIMESTAMP " +
"WHERE id=:id", { "WHERE id=:id", {
replacements: asset, replacements: asset,
}); });
@ -520,6 +520,7 @@ function scanDir(parent, path) {
mtime: stats.mtime, mtime: stats.mtime,
ctime: stats.ctime ctime: stats.ctime
}, },
size: stats.size,
album: album album: album
}); });
}); });
@ -588,8 +589,8 @@ function findOrUpdateDBAsset(transaction, asset) {
}).then(function(results) { }).then(function(results) {
if (results.length == 0) { if (results.length == 0) {
return photoDB.sequelize.query("INSERT INTO photos " + return photoDB.sequelize.query("INSERT INTO photos " +
"(albumId,filename,name) " + "(albumId,filename,name,size) " +
"VALUES(:albumId,:filename,:name)", { "VALUES(:albumId,:filename,:name,:size)", {
replacements: asset, replacements: asset,
transaction: transaction transaction: transaction
}).spread(function(results, metadata) { }).spread(function(results, metadata) {