Skip to content
Browse files

Switch to hapi 0.6 route config format

  • Loading branch information...
1 parent 4b8e590 commit 50f3088e61236293c2a9c57de32214f7545eff71 @hueniverse committed
Showing with 2,260 additions and 2,065 deletions.
  1. +108 −108 api/batch.js
  2. +98 −86 api/details.js
  3. +19 −9 api/email.js
  4. +107 −99 api/invite.js
  5. +86 −74 api/last.js
  6. +486 −455 api/project.js
  7. +57 −56 api/routes.js
  8. +19 −13 api/session.js
  9. +121 −115 api/storage.js
  10. +63 −57 api/stream.js
  11. +62 −59 api/suggestions.js
  12. +238 −211 api/task.js
  13. +794 −716 api/user.js
  14. +2 −7 web/views/console.jade
View
216 api/batch.js
@@ -16,200 +16,200 @@ var Config = require('./config');
var internals = {};
-// Type definition
-
-exports.type = {
+// Batch processing
- get: { type: 'string', array: true, required: true }
-};
+exports.post = {
+ schema: {
-// Batch processing
+ get: { type: 'string', array: true, required: true }
+ },
+
+ handler: function (request) {
-exports.post = function (request) {
+ var requests = [];
+ var results = [];
+ var resultsMap = {};
- var requests = [];
- var results = [];
- var resultsMap = {};
+ function entry() {
- function entry() {
+ var requestRegex = /(?:\/)(?:\$(\d)+\.)?([\w:\.]+)/g; // /project/$1.project/tasks, does not allow using array responses
- var requestRegex = /(?:\/)(?:\$(\d)+\.)?([\w:\.]+)/g; // /project/$1.project/tasks, does not allow using array responses
+ // Validate requests
- // Validate requests
+ var error = null;
+ var parseRequest = function ($0, $1, $2) {
- var error = null;
- var parseRequest = function ($0, $1, $2) {
+ if ($1) {
- if ($1) {
+ if ($1 < i) {
- if ($1 < i) {
+ if ($1.indexOf(':') === -1) {
- if ($1.indexOf(':') === -1) {
+ parts.push({ type: 'ref', index: $1, value: $2 });
+ return '';
+ }
+ else {
- parts.push({ type: 'ref', index: $1, value: $2 });
- return '';
+ error = 'Request reference includes invalid ":" character (' + i + ')';
+ return $0;
+ }
}
else {
- error = 'Request reference includes invalid ":" character (' + i + ')';
+ error = 'Request reference is beyond array size (' + i + ')';
return $0;
}
}
else {
- error = 'Request reference is beyond array size (' + i + ')';
- return $0;
+ parts.push({ type: 'text', value: $2 });
+ return '';
}
- }
- else {
+ };
- parts.push({ type: 'text', value: $2 });
- return '';
- }
- };
+ for (var i = 0, il = request.payload.get.length; i < il; ++i) {
+
+ // Break into parts
- for (var i = 0, il = request.payload.get.length; i < il; ++i) {
+ var parts = [];
+ var result = request.payload.get[i].replace(requestRegex, parseRequest);
- // Break into parts
+ // Make sure entire string was processed (empty)
- var parts = [];
- var result = request.payload.get[i].replace(requestRegex, parseRequest);
+ if (result === '') {
+
+ requests.push(parts);
+ }
+ else {
- // Make sure entire string was processed (empty)
+ error = error || 'Invalid request format (' + i + ')';
+ break;
+ }
+ }
- if (result === '') {
+ if (error === null) {
- requests.push(parts);
+ process();
}
else {
- error = error || 'Invalid request format (' + i + ')';
- break;
+ request.reply(Hapi.Error.badRequest(error));
}
}
- if (error === null) {
-
- process();
- }
- else {
+ function process() {
- request.reply(Hapi.Error.badRequest(error));
- }
- }
+ batch(0, function () {
- function process() {
+ // Return results
- batch(0, function () {
+ request.reply(results);
+ });
+ }
- // Return results
+ function batch(pos, callback) {
- request.reply(results);
- });
- }
+ if (pos >= requests.length) {
- function batch(pos, callback) {
+ callback();
+ }
+ else {
- if (pos >= requests.length) {
+ // Prepare request
- callback();
- }
- else {
-
- // Prepare request
+ var parts = requests[pos];
+ var path = '';
+ var error = null;
- var parts = requests[pos];
- var path = '';
- var error = null;
+ for (var i = 0, il = parts.length; i < il; ++i) {
- for (var i = 0, il = parts.length; i < il; ++i) {
+ path += '/';
- path += '/';
+ if (parts[i].type === 'ref') {
- if (parts[i].type === 'ref') {
+ var ref = resultsMap[parts[i].index];
+ if (ref) {
- var ref = resultsMap[parts[i].index];
- if (ref) {
+ var value = null;
- var value = null;
+ try {
- try {
+ eval('value = ref.' + parts[i].value + ';');
+ }
+ catch (e) {
- eval('value = ref.' + parts[i].value + ';');
- }
- catch (e) {
+ error = e.message;
+ }
- error = e.message;
- }
+ if (value) {
- if (value) {
+ if (value.match(/^[\w:]+$/)) {
- if (value.match(/^[\w:]+$/)) {
+ path += value;
+ }
+ else {
- path += value;
+ error = 'Reference value includes illegal characters';
+ break;
+ }
}
else {
- error = 'Reference value includes illegal characters';
+ error = error || 'Reference not found';
break;
}
}
else {
- error = error || 'Reference not found';
+ error = 'Missing reference response';
break;
}
}
else {
- error = 'Missing reference response';
- break;
+ path += parts[i].value;
}
}
- else {
- path += parts[i].value;
- }
- }
-
- if (error === null) {
+ if (error === null) {
- // Make request
+ // Make request
- internals.call('GET', path, null, request.session, function (data, err) {
+ internals.call('GET', path, null, request.session, function (data, err) {
- if (err === null) {
+ if (err === null) {
- // Process response
+ // Process response
- results.push(data);
- resultsMap[pos] = data;
- }
- else {
+ results.push(data);
+ resultsMap[pos] = data;
+ }
+ else {
- results.push(err);
- }
+ results.push(err);
+ }
- // Call next
+ // Call next
- batch(pos + 1, callback);
- });
- }
- else {
+ batch(pos + 1, callback);
+ });
+ }
+ else {
- // Set error response (as string)
+ // Set error response (as string)
- results.push(error);
+ results.push(error);
- // Call next
+ // Call next
- batch(pos + 1, callback);
+ batch(pos + 1, callback);
+ }
}
}
- }
- entry();
+ entry();
+ }
};
View
184 api/details.js
@@ -13,149 +13,161 @@ var User = require('./user');
var Stream = require('./stream');
-// Type definition
-
-exports.type = {
+// Task details
- created: { type: 'number', set: false },
- type: { type: 'enum', required: true, values: { text: 1 } },
- content: { type: 'string', required: true },
- user: { type: 'id', set: false }
-};
+exports.get = {
+
+ query: {
+ since: Hapi.Types.Number().min(0)
+ },
-// Task details
+ handler: function (request) {
-exports.get = function (request) {
+ exports.load(request.params.id, request.userId, false, function (details, err, task, project) {
- exports.load(request.params.id, request.userId, false, function (details, err, task, project) {
+ details = details || { id: request.params.id, thread: [] };
- details = details || { id: request.params.id, thread: [] };
-
- if (err === null) {
+ if (err === null) {
- // Clear thread from old entries
+ // Clear thread from old entries
- if (request.query.since) {
+ if (request.query.since) {
- var since = parseInt(request.query.since, 10);
- if (since &&
- since > 0) {
+ var since = parseInt(request.query.since, 10);
+ if (since &&
+ since > 0) {
- var thread = [];
- for (var i = 0, il = details.thread.length; i < il; ++i) {
+ var thread = [];
+ for (var i = 0, il = details.thread.length; i < il; ++i) {
- if (details.thread[i].created > since) {
+ if (details.thread[i].created > since) {
- thread.push(details.thread[i]);
+ thread.push(details.thread[i]);
+ }
}
- }
- details.thread = thread;
+ details.thread = thread;
+ }
}
- }
- // Load user display information
+ // Load user display information
- var userIds = [];
- for (i = 0, il = details.thread.length; i < il; ++i) {
+ var userIds = [];
+ for (i = 0, il = details.thread.length; i < il; ++i) {
- userIds.push(details.thread[i].user);
- }
+ userIds.push(details.thread[i].user);
+ }
- User.expandIds(userIds, function (users, usersMap) {
+ User.expandIds(userIds, function (users, usersMap) {
- // Assign to each thread item
+ // Assign to each thread item
- for (i = 0, il = details.thread.length; i < il; ++i) {
+ for (i = 0, il = details.thread.length; i < il; ++i) {
- details.thread[i].user = usersMap[details.thread[i].user] || { id: details.thread[i].user };
- }
+ details.thread[i].user = usersMap[details.thread[i].user] || { id: details.thread[i].user };
+ }
- request.reply(details);
- });
- }
- else {
+ request.reply(details);
+ });
+ }
+ else {
- request.reply(err);
- }
- });
+ request.reply(err);
+ }
+ });
+ }
};
// Add task detail
-exports.post = function (request) {
+exports.post = {
+
+ query: {
- var now = Hapi.Utils.getTimestamp();
+ last: Hapi.Types.Boolean()
+ },
- exports.load(request.params.id, request.userId, true, function (details, err, task, project) {
+ schema: {
- if (task) {
+ created: { type: 'number', set: false },
+ type: { type: 'enum', required: true, values: { text: 1 } },
+ content: { type: 'string', required: true },
+ user: { type: 'id', set: false }
+ },
- if (err === null) {
+ handler: function (request) {
- var detail = request.payload;
- detail.created = now;
- detail.user = request.userId;
+ var now = Hapi.Utils.getTimestamp();
- if (details) {
+ exports.load(request.params.id, request.userId, true, function (details, err, task, project) {
- // Existing details
+ if (task) {
- Db.update('task.details', details._id, { $push: { thread: detail} }, function (err) {
+ if (err === null) {
- if (err === null) {
+ var detail = request.payload;
+ detail.created = now;
+ detail.user = request.userId;
- finalize(task, project);
- }
- else {
+ if (details) {
- request.reply(err);
- }
- });
- }
- else {
+ // Existing details
- // First detail
+ Db.update('task.details', details._id, { $push: { thread: detail } }, function (err) {
- details = { _id: task._id, project: project._id, thread: [] };
- details.thread.push(detail);
+ if (err === null) {
- Db.insert('task.details', details, function (items, err) {
+ finalize(task, project);
+ }
+ else {
- if (err === null) {
+ request.reply(err);
+ }
+ });
+ }
+ else {
- finalize(task, project);
- }
- else {
+ // First detail
- request.reply(err);
- }
- });
+ details = { _id: task._id, project: project._id, thread: [] };
+ details.thread.push(detail);
+
+ Db.insert('task.details', details, function (items, err) {
+
+ if (err === null) {
+
+ finalize(task, project);
+ }
+ else {
+
+ request.reply(err);
+ }
+ });
+ }
+ }
+ else {
+
+ request.reply(err);
}
}
else {
request.reply(err);
}
- }
- else {
+ });
- request.reply(err);
- }
- });
+ function finalize(task, project) {
- function finalize(task, project) {
+ if (request.query.last === 'true') {
- if (request.query.last &&
- request.query.last === 'true') {
+ Last.setLast(request.userId, project, task, function (err) { }); // Ignore response
+ }
- Last.setLast(request.userId, project, task, function (err) {}); // Ignore response
+ Stream.update({ object: 'details', project: task.project, task: task._id }, request);
+ request.reply({ status: 'ok' });
}
-
- Stream.update({ object: 'details', project: task.project, task: task._id }, request);
- request.reply({ status: 'ok' });
}
};
View
28 api/email.js
@@ -12,6 +12,11 @@ var User = require('./user');
var Config = require('./config');
+// Declare internals
+
+var internals = {};
+
+
// Generate email ticket
exports.generateTicket = function (user, email, arg1, arg2) {
@@ -274,7 +279,7 @@ exports.sendReminder = function (user, callback) {
'Use this link to sign into ' + Config.product.name + ': \n\n' +
' ' + Config.host.uri('web') + '/t/' + ticket;
- Hapi.Email.send(user.emails[0].address, subject, text);
+ internals.sendEmail(user.emails[0].address, subject, text);
callback(null);
}
else {
@@ -306,7 +311,7 @@ exports.sendValidation = function (user, address, callback) {
'Use this link to verify your email address: \n\n' +
' ' + Config.host.uri('web') + '/t/' + ticket;
- Hapi.Email.send(address, subject, text);
+ internals.sendEmail(address, subject, text);
callback(null);
}
else {
@@ -349,7 +354,7 @@ exports.sendWelcome = function (user, callback) {
text += 'Use this link to verify your email address: \n\n';
text += ' ' + Config.host.uri('web') + '/t/' + ticket + '\n\n';
- Hapi.Email.send(address, subject, text);
+ internals.sendEmail(address, subject, text);
callback(null);
}
else {
@@ -367,7 +372,7 @@ exports.sendWelcome = function (user, callback) {
text += 'Use this link to sign-into ' + Config.product.name + ': \n\n';
text += ' ' + Config.host.uri('web') + '/\n\n';
- Hapi.Email.send(address, subject, text);
+ internals.sendEmail(address, subject, text);
callback(null);
}
else {
@@ -382,7 +387,7 @@ exports.sendWelcome = function (user, callback) {
text += 'Since you have not yet linked a Facebook, Twitter, or Yahoo! account, you will need to use this link to sign back into ' + Config.product.name + ': \n\n';
text += ' ' + Config.host.uri('web') + '/t/' + ticket + '\n\n';
- Hapi.Email.send(address, subject, text);
+ internals.sendEmail(address, subject, text);
callback(null);
}
else {
@@ -428,9 +433,9 @@ exports.projectInvite = function (users, pids, project, message, inviter) {
link = 'Use this link to join: \n\n' +
' ' + Config.host.uri('web') + '/view/#project=' + project._id;
- Hapi.Email.send(users[i].emails[0].address,
- subject,
- 'Hi ' + (users[i].name || users[i].username || users[i].emails[0].address) + ',\n\n' + text + link);
+ internals.sendEmail(users[i].emails[0].address,
+ subject,
+ 'Hi ' + (users[i].name || users[i].username || users[i].emails[0].address) + ',\n\n' + text + link);
}
}
@@ -447,7 +452,7 @@ exports.projectInvite = function (users, pids, project, message, inviter) {
link = 'Use this link to join: \n\n' +
' ' + Config.host.uri('web') + '/i/' + invite;
- Hapi.Email.send(pid.email, subject, 'Hi ' + (pid.display || pid.email) + ',\n\n' + text + link, null, function (err) {
+ internals.sendEmail(pid.email, subject, 'Hi ' + (pid.display || pid.email) + ',\n\n' + text + link, function (err) {
if (err === null) {
@@ -468,5 +473,10 @@ exports.projectInvite = function (users, pids, project, message, inviter) {
};
+internals.sendEmail = function (address, subject, text, callback) {
+
+ Hapi.Email.send(address, subject, text, '', Config.email, callback);
+};
+
View
206 api/invite.js
@@ -14,168 +14,176 @@ var Stream = require('./stream');
// Check invitation code
-exports.get = function (request) {
+exports.get = {
+
+ authentication: 'none',
- // Check invitation code type
+ handler: function (request) {
- var inviteRegex = /^project:([^:]+):([^:]+):([^:]+)$/;
- var parts = inviteRegex.exec(request.params.id);
+ // Check invitation code type
- if (parts &&
- parts.length === 4) {
+ var inviteRegex = /^project:([^:]+):([^:]+):([^:]+)$/;
+ var parts = inviteRegex.exec(request.params.id);
- // Project invitation code
+ if (parts &&
+ parts.length === 4) {
- var projectId = parts[1];
- var pid = parts[2];
- var code = parts[3];
+ // Project invitation code
- // Load project (not using Project.load since active user is not a member)
+ var projectId = parts[1];
+ var pid = parts[2];
+ var code = parts[3];
- Db.get('project', projectId, function (project, err) {
+ // Load project (not using Project.load since active user is not a member)
- if (project) {
+ Db.get('project', projectId, function (project, err) {
- // Lookup code
+ if (project) {
- var projectPid = null;
+ // Lookup code
- for (var i = 0, il = project.participants.length; i < il; ++i) {
+ var projectPid = null;
- if (project.participants[i].pid &&
- project.participants[i].pid === pid) {
+ for (var i = 0, il = project.participants.length; i < il; ++i) {
- if (project.participants[i].code &&
- project.participants[i].code === code) {
+ if (project.participants[i].pid &&
+ project.participants[i].pid === pid) {
- projectPid = project.participants[i];
- break;
- }
- else {
+ if (project.participants[i].code &&
+ project.participants[i].code === code) {
+
+ projectPid = project.participants[i];
+ break;
+ }
+ else {
- // Invalid code
- break;
+ // Invalid code
+ break;
+ }
}
}
- }
- if (projectPid) {
+ if (projectPid) {
- User.quick(projectPid.inviter, function (inviter) {
+ User.quick(projectPid.inviter, function (inviter) {
- var about = { title: project.title, project: project._id };
+ var about = { title: project.title, project: project._id };
- if (inviter &&
- inviter.display) {
+ if (inviter &&
+ inviter.display) {
- about.inviter = inviter.display;
- }
+ about.inviter = inviter.display;
+ }
+
+ request.reply(about);
+ });
+ }
+ else {
- request.reply(about);
- });
+ request.reply(Hapi.Error.badRequest('Invalid invitation code'));
+ }
}
else {
- request.reply(Hapi.Error.badRequest('Invalid invitation code'));
+ request.reply(err);
}
- }
- else {
-
- request.reply(err);
- }
- });
- }
- else {
+ });
+ }
+ else {
- // Registration invitation code
+ // Registration invitation code
- exports.load(request.params.id, function (invite, err) {
+ exports.load(request.params.id, function (invite, err) {
- if (err === null) {
+ if (err === null) {
- request.reply(invite);
- }
- else {
+ request.reply(invite);
+ }
+ else {
- request.reply(err);
- }
- });
+ request.reply(err);
+ }
+ });
+ }
}
};
// Claim a project invitation
-exports.claim = function (request) {
+exports.claim = {
+
+ handler: function (request) {
- var inviteRegex = /^project:([^:]+):([^:]+):([^:]+)$/;
- var parts = inviteRegex.exec(request.params.id);
+ var inviteRegex = /^project:([^:]+):([^:]+):([^:]+)$/;
+ var parts = inviteRegex.exec(request.params.id);
- if (parts &&
- parts.length === 4) {
+ if (parts &&
+ parts.length === 4) {
- var projectId = parts[1];
- var pid = parts[2];
- var code = parts[3];
+ var projectId = parts[1];
+ var pid = parts[2];
+ var code = parts[3];
- // Load project (not using Project.load since active user is not a member)
+ // Load project (not using Project.load since active user is not a member)
- Db.get('project', projectId, function (project, err) {
+ Db.get('project', projectId, function (project, err) {
- if (project) {
+ if (project) {
- // Lookup code
+ // Lookup code
- var projectPid = null;
+ var projectPid = null;
- for (var i = 0, il = project.participants.length; i < il; ++i) {
+ for (var i = 0, il = project.participants.length; i < il; ++i) {
- if (project.participants[i].pid &&
- project.participants[i].pid === pid) {
+ if (project.participants[i].pid &&
+ project.participants[i].pid === pid) {
- if (project.participants[i].code &&
- project.participants[i].code === code) {
+ if (project.participants[i].code &&
+ project.participants[i].code === code) {
- projectPid = project.participants[i];
- break;
- }
- else {
+ projectPid = project.participants[i];
+ break;
+ }
+ else {
- // Invalid code
- break;
+ // Invalid code
+ break;
+ }
}
}
- }
- if (projectPid) {
+ if (projectPid) {
- Project.replacePid(project, projectPid.pid, request.userId, function (err) {
+ Project.replacePid(project, projectPid.pid, request.userId, function (err) {
- if (err === null) {
+ if (err === null) {
- Stream.update({ object: 'project', project: projectId }, request);
- request.reply({ status: 'ok', project: projectId });
- }
- else {
+ Stream.update({ object: 'project', project: projectId }, request);
+ request.reply({ status: 'ok', project: projectId });
+ }
+ else {
- request.reply(err);
- }
- });
+ request.reply(err);
+ }
+ });
+ }
+ else {
+
+ request.reply(Hapi.Error.badRequest('Invalid invitation code'));
+ }
}
else {
- request.reply(Hapi.Error.badRequest('Invalid invitation code'));
+ request.reply(err);
}
- }
- else {
-
- request.reply(err);
- }
- });
- }
- else {
+ });
+ }
+ else {
- request.reply(Hapi.Error.badRequest('Invalid invitation format'));
+ request.reply(Hapi.Error.badRequest('Invalid invitation format'));
+ }
}
};
View
160 api/last.js
@@ -18,124 +18,136 @@ var internals = {};
// Last information for project (with tasks)
-exports.getProject = function (request) {
+exports.getProject = {
+
+ handler: function (request) {
- exports.load(request.userId, function (last, err) {
+ exports.load(request.userId, function (last, err) {
- if (last &&
- last.projects &&
- last.projects[request.params.id]) {
+ if (last &&
+ last.projects &&
+ last.projects[request.params.id]) {
- var record = { id: last._id, projects: {} };
- record.projects[request.params.id] = last.projects[request.params.id];
+ var record = { id: last._id, projects: {} };
+ record.projects[request.params.id] = last.projects[request.params.id];
- request.reply(record);
- }
- else if (err === null) {
+ request.reply(record);
+ }
+ else if (err === null) {
- request.reply({ id: request.userId, projects: {} });
- }
- else {
+ request.reply({ id: request.userId, projects: {} });
+ }
+ else {
- request.reply(err);
- }
- });
+ request.reply(err);
+ }
+ });
+ }
};
// Set last project timestamp
-exports.postProject = function (request) {
+exports.postProject = {
+
+ handler: function (request) {
- Project.load(request.params.id, request.userId, false, function (project, member, err) {
+ Project.load(request.params.id, request.userId, false, function (project, member, err) {
- if (project) {
+ if (project) {
- exports.setLast(request.userId, project, null, function (err) {
+ exports.setLast(request.userId, project, null, function (err) {
- if (err === null) {
+ if (err === null) {
- request.reply({ status: 'ok' });
- }
- else {
+ request.reply({ status: 'ok' });
+ }
+ else {
- request.reply(err);
- }
- });
- }
- else {
+ request.reply(err);
+ }
+ });
+ }
+ else {
- request.reply(err);
- }
- });
+ request.reply(err);
+ }
+ });
+ }
};
// Last information for single task
-exports.getTask = function (request) {
+exports.getTask = {
+
+ handler: function (request) {
- Task.load(request.params.id, request.userId, false, function (task, err, project) {
+ Task.load(request.params.id, request.userId, false, function (task, err, project) {
- if (task) {
+ if (task) {
- exports.load(request.userId, function (last, err) {
+ exports.load(request.userId, function (last, err) {
- if (last &&
- last.projects &&
- last.projects[task.project] &&
- last.projects[task.project].tasks &&
- last.projects[task.project].tasks[request.params.id]) {
+ if (last &&
+ last.projects &&
+ last.projects[task.project] &&
+ last.projects[task.project].tasks &&
+ last.projects[task.project].tasks[request.params.id]) {
- var record = { id: last._id, projects: {} };
- record.projects[task.project] = { tasks: {} };
- record.projects[task.project].tasks[request.params.id] = last.projects[task.project].tasks[request.params.id];
+ var record = { id: last._id, projects: {} };
+ record.projects[task.project] = { tasks: {} };
+ record.projects[task.project].tasks[request.params.id] = last.projects[task.project].tasks[request.params.id];
- request.reply(record);
- }
- else if (err === null) {
+ request.reply(record);
+ }
+ else if (err === null) {
- request.reply({ id: request.userId, projects: {} });
- }
- else {
+ request.reply({ id: request.userId, projects: {} });
+ }
+ else {
- request.reply(err);
- }
- });
- }
- else {
+ request.reply(err);
+ }
+ });
+ }
+ else {
- request.reply(err);
- }
- });
+ request.reply(err);
+ }
+ });
+ }
};
// Set last task timestamp
-exports.postTask = function (request) {
+exports.postTask = {
+
+ handler: function (request) {
- Task.load(request.params.id, request.userId, false, function (task, err, project) {
+ Task.load(request.params.id, request.userId, false, function (task, err, project) {
- if (task) {
+ if (task) {
- exports.setLast(request.userId, project, task, function (err) {
+ exports.setLast(request.userId, project, task, function (err) {
- if (err === null) {
+ if (err === null) {
- request.reply({ status: 'ok' });
- }
- else {
+ request.reply({ status: 'ok' });
+ }
+ else {
- request.reply(err);
- }
- });
- }
- else {
+ request.reply(err);
+ }
+ });
+ }
+ else {
- request.reply(err);
- }
- });
+ request.reply(err);
+ }
+ });
+ }
};
View
941 api/project.js
@@ -19,149 +19,160 @@ var Stream = require('./stream');
// Declare internals
-var internals = {
-
- maxMessageLength: 250
-};
-
-
-// Project definitions
-
-exports.type = {};
-
-exports.type.post = {
-
- title: { type: 'string' },
- date: { type: 'date', empty: true },
- time: { type: 'time', empty: true },
- place: { type: 'string', empty: true },
- participants: { type: 'object', set: false, array: true }
-};
-
-exports.type.put = Hapi.Utils.clone(exports.type.post);
-exports.type.put.title.required = true;
-
-exports.type.participants = {
-
- participants: { type: 'id', array: true }, // type can also be email
- names: { type: 'string', array: true }
-};
-
-exports.type.uninvite = {
-
- participants: { type: 'id', array: true, required: true }
-};
+var internals = {};
// Get project information
-exports.get = function (request) {
+exports.get = {
+
+ handler: function (request) {
- exports.load(request.params.id, request.userId, false, function (project, member, err) {
+ exports.load(request.params.id, request.userId, false, function (project, member, err) {
- if (project) {
+ if (project) {
- exports.participantsList(project, function (participants) {
+ exports.participantsList(project, function (participants) {
- project.participants = participants;
+ project.participants = participants;
- request.reply(project);
- });
- }
- else {
+ request.reply(project);
+ });
+ }
+ else {
- request.reply(err);
- }
- });
+ request.reply(err);
+ }
+ });
+ }
};
// Get list of projects for current user
-exports.list = function (request) {
+exports.list = {
+
+ handler: function (request) {
- Sort.list('project', request.userId, 'participants.id', function (projects) {
+ Sort.list('project', request.userId, 'participants.id', function (projects) {
- if (projects) {
+ if (projects) {
- var list = [];
- for (var i = 0, il = projects.length; i < il; ++i) {
+ var list = [];
+ for (var i = 0, il = projects.length; i < il; ++i) {
- var isPending = false;
- for (var p = 0, pl = projects[i].participants.length; p < pl; ++p) {
+ var isPending = false;
+ for (var p = 0, pl = projects[i].participants.length; p < pl; ++p) {
- if (projects[i].participants[p].id &&
- projects[i].participants[p].id === request.userId) {
+ if (projects[i].participants[p].id &&
+ projects[i].participants[p].id === request.userId) {
- isPending = projects[i].participants[p].isPending || false;
- break;
+ isPending = projects[i].participants[p].isPending || false;
+ break;
+ }
}
- }
- var item = { id: projects[i]._id, title: projects[i].title };
+ var item = { id: projects[i]._id, title: projects[i].title };
- if (isPending) {
+ if (isPending) {
- item.isPending = true;
- }
+ item.isPending = true;
+ }
- list.push(item);
- }
+ list.push(item);
+ }
- Last.load(request.userId, function (last, err) {
+ Last.load(request.userId, function (last, err) {
- if (last &&
- last.projects) {
+ if (last &&
+ last.projects) {
- for (i = 0, il = list.length; i < il; ++i) {
+ for (i = 0, il = list.length; i < il; ++i) {
- if (last.projects[list[i].id]) {
+ if (last.projects[list[i].id]) {
- list[i].last = last.projects[list[i].id].last;
+ list[i].last = last.projects[list[i].id].last;
+ }
}
}
- }
- request.reply(list);
- });
- }
- else {
+ request.reply(list);
+ });
+ }
+ else {
- request.reply(Hapi.Error.notFound());
- }
- });
+ request.reply(Hapi.Error.notFound());
+ }
+ });
+ }
};
// Update project properties
-exports.post = function (request) {
+exports.post = {
+
+ query: {
- exports.load(request.params.id, request.userId, true, function (project, member, err) {
+ position: Hapi.Types.Number().min(0)
+ },
- if (project) {
+ schema: {
- if (Object.keys(request.payload).length > 0) {
+ title: { type: 'string' },
+ date: { type: 'date', empty: true },
+ time: { type: 'time', empty: true },
+ place: { type: 'string', empty: true },
+ participants: { type: 'object', set: false, array: true }
+ },
- if (request.query.position === undefined) {
+ handler: function (request) {
- Db.update('project', project._id, Db.toChanges(request.payload), function (err) {
+ exports.load(request.params.id, request.userId, true, function (project, member, err) {
- if (err === null) {
+ if (project) {
- Stream.update({ object: 'project', project: project._id }, request);
+ if (Object.keys(request.payload).length > 0) {
- if (request.payload.title !== project.title) {
+ if (request.query.position === undefined) {
- for (var i = 0, il = project.participants.length; i < il; ++i) {
+ Db.update('project', project._id, Db.toChanges(request.payload), function (err) {
- if (project.participants[i].id) {
+ if (err === null) {
- Stream.update({ object: 'projects', user: project.participants[i].id }, request);
+ Stream.update({ object: 'project', project: project._id }, request);
+
+ if (request.payload.title !== project.title) {
+
+ for (var i = 0, il = project.participants.length; i < il; ++i) {
+
+ if (project.participants[i].id) {
+
+ Stream.update({ object: 'projects', user: project.participants[i].id }, request);
+ }
}
}
+
+ request.reply({ status: 'ok' });
}
+ else {
+
+ request.reply(err);
+ }
+ });
+ }
+ else {
+
+ request.reply(Hapi.Error.badRequest('Cannot include both position parameter and project object in body'));
+ }
+ }
+ else if (request.query.position) {
+
+ Sort.set('project', request.userId, 'participants.id', request.params.id, request.query.position, function (err) {
+ if (err === null) {
+
+ Stream.update({ object: 'projects', user: request.userId }, request);
request.reply({ status: 'ok' });
}
else {
@@ -172,196 +183,209 @@ exports.post = function (request) {
}
else {
- request.reply(Hapi.Error.badRequest('Cannot include both position parameter and project object in body'));
+ request.reply(Hapi.Error.badRequest('Missing position parameter or project object in body'));
}
}
- else if (request.query.position) {
-
- Sort.set('project', request.userId, 'participants.id', request.params.id, request.query.position, function (err) {
-
- if (err === null) {
-
- Stream.update({ object: 'projects', user: request.userId }, request);
- request.reply({ status: 'ok' });
- }
- else {
-
- request.reply(err);
- }
- });
- }
else {
- request.reply(Hapi.Error.badRequest('Missing position parameter or project object in body'));
+ request.reply(err);
}
- }
- else {
-
- request.reply(err);
- }
- });
+ });
+ }
};
// Create new project
-exports.put = function (request) {
+exports.put = {
+
+ schema: {
- var project = request.payload;
- project.participants = [{ id: request.userId}];
+ title: { type: 'string', required: true },
+ date: { type: 'date', empty: true },
+ time: { type: 'time', empty: true },
+ place: { type: 'string', empty: true },
+ participants: { type: 'object', set: false, array: true }
+ },
- Db.insert('project', project, function (items, err) {
+ handler: function (request) {
- if (err === null) {
+ var project = request.payload;
+ project.participants = [{ id: request.userId }];
- Stream.update({ object: 'projects', user: request.userId }, request);
- request.reply({ status: 'ok', id: items[0]._id }, { created: 'project/' + items[0]._id });
- }
- else {
+ Db.insert('project', project, function (items, err) {
- request.reply(err);
- }
- });
+ if (err === null) {
+
+ Stream.update({ object: 'projects', user: request.userId }, request);
+ request.reply({ status: 'ok', id: items[0]._id }, { created: 'project/' + items[0]._id });
+ }
+ else {
+
+ request.reply(err);
+ }
+ });
+ }
};
// Delete a project
-exports.del = function (request) {
+exports.del = {
+
+ handler: function (request) {
- exports.load(request.params.id, request.userId, false, function (project, member, err) {
+ exports.load(request.params.id, request.userId, false, function (project, member, err) {
- if (project) {
+ if (project) {
- // Check if owner
+ // Check if owner
- if (exports.isOwner(project, request.userId)) {
+ if (exports.isOwner(project, request.userId)) {
- // Delete all tasks
+ // Delete all tasks
- Task.delProject(project._id, function (err) {
+ Task.delProject(project._id, function (err) {
- if (err === null) {
+ if (err === null) {
- // Delete project
+ // Delete project
- Db.remove('project', project._id, function (err) {
+ Db.remove('project', project._id, function (err) {
- if (err === null) {
+ if (err === null) {
- Last.delProject(request.userId, project._id, function (err) { });
+ Last.delProject(request.userId, project._id, function (err) { });
- Stream.update({ object: 'project', project: project._id }, request);
+ Stream.update({ object: 'project', project: project._id }, request);
- for (var i = 0, il = project.participants.length; i < il; ++i) {
+ for (var i = 0, il = project.participants.length; i < il; ++i) {
- if (project.participants[i].id) {
+ if (project.participants[i].id) {
- Stream.update({ object: 'projects', user: project.participants[i].id }, request);
- Stream.drop(project.participants[i].id, project._id);
+ Stream.update({ object: 'projects', user: project.participants[i].id }, request);
+ Stream.drop(project.participants[i].id, project._id);
+ }
}
- }
- request.reply({ status: 'ok' });
- }
- else {
+ request.reply({ status: 'ok' });
+ }
+ else {
- request.reply(err);
- }
- });
- }
- else {
+ request.reply(err);
+ }
+ });
+ }
+ else {
- request.reply(err);
- }
- });
- }
- else {
+ request.reply(err);
+ }
+ });
+ }
+ else {
- // Leave project
+ // Leave project
- internals.leave(project, member, function (err) {
+ internals.leave(project, member, function (err) {
- if (err === null) {
+ if (err === null) {
- Stream.update({ object: 'project', project: project._id }, request);
- Stream.update({ object: 'projects', user: request.userId }, request);
- Stream.drop(request.userId, project._id);
+ Stream.update({ object: 'project', project: project._id }, request);
+ Stream.update({ object: 'projects', user: request.userId }, request);
+ Stream.drop(request.userId, project._id);
- request.reply({ status: 'ok' });
- }
- else {
+ request.reply({ status: 'ok' });
+ }
+ else {
- request.reply(err);
- }
- });
+ request.reply(err);
+ }
+ });
+ }
}
- }
- else {
+ else {
- request.reply(err);
- }
- });
+ request.reply(err);
+ }
+ });
+ }
};
// Get list of project tips
-exports.tips = function (request) {
+exports.tips = {
+
+ handler: function (request) {
- // Get project
+ // Get project
- exports.load(request.params.id, request.userId, false, function (project, member, err) {
+ exports.load(request.params.id, request.userId, false, function (project, member, err) {
- if (project) {
+ if (project) {
- // Collect tips
+ // Collect tips
- Tips.list(project, function (results) {
+ Tips.list(project, function (results) {
- request.reply(results);
- });
- }
- else {
+ request.reply(results);
+ });
+ }
+ else {
- request.reply(err);
- }
- });
+ request.reply(err);
+ }
+ });
+ }
};
// Get list of project suggestions
-exports.suggestions = function (request) {
+exports.suggestions = {
+
+ handler: function (request) {
- // Get project
+ // Get project
- exports.load(request.params.id, request.userId, false, function (project, member, err) {
+ exports.load(request.params.id, request.userId, false, function (project, member, err) {
- if (project) {
+ if (project) {
- // Collect tips
+ // Collect tips
- Suggestions.list(project, request.userId, function (results) {
+ Suggestions.list(project, request.userId, function (results) {
- request.reply(results);
- });
- }
- else {
+ request.reply(results);
+ });
+ }
+ else {
- request.reply(err);
- }
- });
+ request.reply(err);
+ }
+ });
+ }
};
// Add new participants to a project
-exports.participants = function (request) {
+exports.participants = {
+
+ query: {
+
+ message: Hapi.Types.String().max(250)
+ },
+
+ schema: {
+
+ participants: { type: 'id', array: true }, // type can also be email
+ names: { type: 'string', array: true }
+ },
- if (request.query.message) {
+ handler: function (request) {
- if (request.query.message.length <= internals.maxMessageLength) {
+ if (request.query.message) {
if (request.query.message.match('://') === null) {
@@ -374,449 +398,456 @@ exports.participants = function (request) {
}
else {
- request.reply(Hapi.Error.badRequest('Message length is greater than ' + internals.maxMessageLength));
+ process();
}
- }
- else {
-
- process();
- }
- function process() {
+ function process() {
- if (request.payload.participants ||
- request.payload.names) {
+ if (request.payload.participants ||
+ request.payload.names) {
- exports.load(request.params.id, request.userId, true, function (project, member, err) {
+ exports.load(request.params.id, request.userId, true, function (project, member, err) {
- if (project) {
+ if (project) {
- var change = { $pushAll: { participants: []} };
+ var change = { $pushAll: { participants: [] } };
- // Add pids (non-users)
+ // Add pids (non-users)
- if (request.payload.names) {
+ if (request.payload.names) {
- for (var i = 0, il = request.payload.names.length; i < il; ++i) {
+ for (var i = 0, il = request.payload.names.length; i < il; ++i) {
- var participant = { pid: Db.generateId(), display: request.payload.names[i] };
- change.$pushAll.participants.push(participant);
- }
+ var participant = { pid: Db.generateId(), display: request.payload.names[i] };
+ change.$pushAll.participants.push(participant);
+ }
- if (request.payload.participants === undefined) {
+ if (request.payload.participants === undefined) {
- // No user accounts to invite, save project
+ // No user accounts to invite, save project
- Db.update('project', project._id, change, function (err) {
+ Db.update('project', project._id, change, function (err) {
- if (err === null) {
+ if (err === null) {
- // Return success
+ // Return success
- finalize();
- }
- else {
+ finalize();
+ }
+ else {
- request.reply(err);
- }
- });
+ request.reply(err);
+ }
+ });
+ }
}
- }
- // Add users or emails
+ // Add users or emails
- if (request.payload.participants) {
+ if (request.payload.participants) {
- // Get user
+ // Get user
- User.load(request.userId, function (user, err) {
+ User.load(request.userId, function (user, err) {
- if (user) {
+ if (user) {
- // Lookup existing users
+ // Lookup existing users
- User.find(request.payload.participants, function (users, emailsNotFound, err) {
+ User.find(request.payload.participants, function (users, emailsNotFound, err) {
- if (err === null) {
+ if (err === null) {
- var prevParticipants = Hapi.Utils.map(project.participants, 'id');
+ var prevParticipants = Hapi.Utils.map(project.participants, 'id');
- // Check for changes
+ // Check for changes
- var contactsChange = { $set: {} };
- var now = Hapi.Utils.getTimestamp();
+ var contactsChange = { $set: {} };
+ var now = Hapi.Utils.getTimestamp();
- var changedUsers = [];
- for (var i = 0, il = users.length; i < il; ++i) {
+ var changedUsers = [];
+ for (var i = 0, il = users.length; i < il; ++i) {
- // Add / update contact
+ // Add / update contact
- if (users[i]._id !== request.userId) {
+ if (users[i]._id !== request.userId) {
- contactsChange.$set['contacts.' + users[i]._id] = { type: 'user', last: now };
- }
+ contactsChange.$set['contacts.' + users[i]._id] = { type: 'user', last: now };
+ }
- // Add participant if new
+ // Add participant if new
- if (prevParticipants[users[i]._id] !== true) {
+ if (prevParticipants[users[i]._id] !== true) {
- change.$pushAll.participants.push({ id: users[i]._id, isPending: true });
- changedUsers.push(users[i]);
+ change.$pushAll.participants.push({ id: users[i]._id, isPending: true });
+ changedUsers.push(users[i]);
+ }
}
- }
- var prevPids = Hapi.Utils.map(project.participants, 'email');
+ var prevPids = Hapi.Utils.map(project.participants, 'email');
- var pids = [];
- for (i = 0, il = emailsNotFound.length; i < il; ++i) {
+ var pids = [];
+ for (i = 0, il = emailsNotFound.length; i < il; ++i) {
- contactsChange.$set['contacts.' + Db.encodeKey(emailsNotFound[i])] = { type: 'email', last: now };
+ contactsChange.$set['contacts.' + Db.encodeKey(emailsNotFound[i])] = { type: 'email', last: now };
- if (prevPids[emailsNotFound[i]] !== true) {
+ if (prevPids[emailsNotFound[i]] !== true) {
- var pid = {
+ var pid = {
- pid: Db.generateId(),
- display: emailsNotFound[i],
- isPending: true,
+ pid: Db.generateId(),
+ display: emailsNotFound[i],
+ isPending: true,
- // Internal fields
+ // Internal fields
- email: emailsNotFound[i],
- code: Hapi.Utils.getRandomString(6),
- inviter: user._id
- };
+ email: emailsNotFound[i],
+ code: Hapi.Utils.getRandomString(6),
+ inviter: user._id
+ };
- change.$pushAll.participants.push(pid);
- pids.push(pid);
+ change.$pushAll.participants.push(pid);
+ pids.push(pid);
+ }
}
- }
- // Update user contacts
+ // Update user contacts
- if (Object.keys(contactsChange.$set).length > 0) {
+ if (Object.keys(contactsChange.$set).length > 0) {
- Db.update('user', user._id, contactsChange, function (err) {
+ Db.update('user', user._id, contactsChange, function (err) {
- // Non-blocking
+ // Non-blocking
- if (err === null) {
+ if (err === null) {
- Stream.update({ object: 'contacts', user: user._id }, request);
- }
- });
- }
+ Stream.update({ object: 'contacts', user: user._id }, request);
+ }
+ });
+ }
- // Update project participants
+ // Update project participants
- if (change.$pushAll.participants.length > 0) {
+ if (change.$pushAll.participants.length > 0) {
- Db.update('project', project._id, change, function (err) {
+ Db.update('project', project._id, change, function (err) {
- if (err === null) {
+ if (err === null) {
- for (var i = 0, il = changedUsers.length; i < il; ++i) {
+ for (var i = 0, il = changedUsers.length; i < il; ++i) {
- Stream.update({ object: 'projects', user: changedUsers[i]._id }, request);
- }
+ Stream.update({ object: 'projects', user: changedUsers[i]._id }, request);
+ }
- // Invite new participants
+ // Invite new participants
- Email.projectInvite(changedUsers, pids, project, request.query.message, user);
+ Email.projectInvite(changedUsers, pids, project, request.query.message, user);
- // Return success
+ // Return success
- finalize();
- }
- else {
+ finalize();
+ }
+ else {
- request.reply(err);
- }
- });
+ request.reply(err);
+ }
+ });
+ }
+ else {
+
+ request.reply(Hapi.Error.badRequest('All users are already project participants'));
+ }
}
else {
- request.reply(Hapi.Error.badRequest('All users are already project participants'));
+ request.reply(err);
}
- }
- else {
-
- request.reply(err);
- }
- });
- }
- else {
+ });
+ }
+ else {
- request.reply(Hapi.Error.internal(err));
- }
- });
+ request.reply(Hapi.Error.internal(err));
+ }
+ });
+ }
}
- }
- else {
+ else {
- request.reply(err);
- }
- });
- }
- else {
+ request.reply(err);
+ }
+ });
+ }
+ else {
- request.reply(Hapi.Error.badRequest('Body must contain a participants or names array'));
+ request.reply(Hapi.Error.badRequest('Body must contain a participants or names array'));
+ }
}
- }
- function finalize() {
+ function finalize() {
- Stream.update({ object: 'project', project: request.params.id }, request);
+ Stream.update({ object: 'project', project: request.params.id }, request);
- // Reload project (changed, use direct DB to skip load processing)
+ // Reload project (changed, use direct DB to skip load processing)
- Db.get('project', request.params.id, function (project, err) {
+ Db.get('project', request.params.id, function (project, err) {
- if (project) {
+ if (project) {
- exports.participantsList(project, function (participants) {
+ exports.participantsList(project, function (participants) {
- var response = { status: 'ok', participants: participants };
+ var response = { status: 'ok', participants: participants };
- request.reply(response);
- });
- }
- else {
+ request.reply(response);
+ });
+ }
+ else {
- request.reply(err);
- }
- });
+ request.reply(err);
+ }
+ });
+ }
}
};
// Remove participant from project
-exports.uninvite = function (request) {
+exports.uninvite = {
+
+ schema: {
- // Load project for write
+ participants: { type: 'id', array: true, required: true }
+ },
- exports.load(request.params.id, request.userId, true, function (project, member, err) {
+ handler: function (request) {
- if (project) {
+ // Load project for write
- // Check if owner
+ exports.load(request.params.id, request.userId, true, function (project, member, err) {
- if (exports.isOwner(project, request.userId)) {
+ if (project) {
- // Check if single delete or batch
+ // Check if owner
- if (request.params.user) {
+ if (exports.isOwner(project, request.userId)) {
- // Single delete
+ // Check if single delete or batch
- if (request.userId !== request.params.user) {
+ if (request.params.user) {
- // Lookup user
+ // Single delete
- var uninvitedMember = exports.getMember(project, request.params.user);
- if (uninvitedMember) {
+ if (request.userId !== request.params.user) {
- internals.leave(project, uninvitedMember, function (err) {
+ // Lookup user
- if (err === null) {
+ var uninvitedMember = exports.getMember(project, request.params.user);
+ if (uninvitedMember) {
- // Return success
+ internals.leave(project, uninvitedMember, function (err) {
- Stream.update({ object: 'projects', user: request.params.user }, request);
- Stream.drop(request.params.user, project._id);
+ if (err === null) {
- finalize();
- }
- else {
+ // Return success
- request.reply(err);
- }
- });
+ Stream.update({ object: 'projects', user: request.params.user }, request);
+ Stream.drop(request.params.user, project._id);
+
+ finalize();
+ }
+ else {
+
+ request.reply(err);
+ }
+ });
+ }
+ else {
+
+ request.reply(Hapi.Error.notFound('Not a project participant'));
+ }
}
else {
- request.reply(Hapi.Error.notFound('Not a project participant'));
+ request.reply(Hapi.Error.badRequest('Cannot uninvite self'));
}
}
- else {
+ else if (request.payload.participants) {
- request.reply(Hapi.Error.badRequest('Cannot uninvite self'));
- }
- }
- else if (request.payload.participants) {
+ // Batch delete