added API endpoints for listing usernames and addresses

This commit is contained in:
Andris Reinman 2017-07-20 13:10:43 +03:00
parent cb745030d4
commit cf20ada049
4 changed files with 243 additions and 16 deletions

229
api.js
View file

@ -11,12 +11,20 @@ const consts = require('./lib/consts');
const UserHandler = require('./lib/user-handler');
const ImapNotifier = require('./lib/imap-notifier');
const db = require('./lib/db');
const MongoPaging = require('mongo-cursor-pagination');
const certs = require('./lib/certs').get('api');
const ObjectID = require('mongodb').ObjectID;
const serverOptions = {
name: 'Wild Duck API',
strictRouting: true
strictRouting: true,
formatters: {
'application/json; q=0.4': (req, res, body) => {
let data = body ? JSON.stringify(body, false, 2) : 'null';
res.setHeader('Content-Length', Buffer.byteLength(data));
return data;
}
}
};
if (certs && config.api.secure) {
@ -32,6 +40,16 @@ const server = restify.createServer(serverOptions);
let userHandler;
let notifier;
// disable compression for EventSource response
// this needs to be called before gzipResponse
server.use((req, res, next) => {
if (req.route.path === '/users/:user/updates') {
req.headers['accept-encoding'] = '';
}
next();
});
server.use(restify.plugins.gzipResponse());
server.use(restify.plugins.queryParser());
server.use(
restify.plugins.bodyParser({
@ -49,6 +67,215 @@ server.get(
})
);
server.get({ name: 'users', path: '/users' }, (req, res, next) => {
res.charSet('utf-8');
const schema = Joi.object().keys({
query: Joi.string().alphanum().lowercase().empty('').max(100),
limit: Joi.number().default(20).min(1).max(250),
next: Joi.string().alphanum().max(100),
prev: Joi.string().alphanum().max(100),
page: Joi.number().default(1)
});
const result = Joi.validate(req.query, schema, {
abortEarly: false,
convert: true,
allowUnknown: true
});
if (result.error) {
res.json({
error: result.error.message
});
return next();
}
let query = result.value.query;
let limit = result.value.limit;
let page = result.value.page;
let pageNext = result.value.next;
let pagePrev = result.value.prev;
let filter = query
? {
username: {
$regex: query,
$options: ''
}
}
: {};
db.users.collection('users').count(filter, (err, total) => {
if (err) {
res.json({
error: result.error.message
});
return next();
}
let opts = {
limit,
query: filter,
fields: {
_id: true,
username: true,
address: true,
storageUsed: true,
quota: true,
setup: true
},
sortAscending: true
};
if (pageNext) {
opts.next = pageNext;
} else if (pagePrev) {
opts.prev = pagePrev;
}
MongoPaging.find(db.users.collection('users'), opts, (err, result) => {
if (err) {
res.json({
error: result.error.message
});
return next();
}
if (!result.hasPrevious) {
page = 1;
}
let prevUrl = result.hasPrevious
? server.router.render('users', {}, { prev: result.previous, limit, query: query || '', page: Math.max(page - 1, 1) })
: false;
let nextUrl = result.hasNext ? server.router.render('users', {}, { next: result.previous, limit, query: query || '', page: page + 1 }) : false;
let response = {
query,
total,
page,
prev: prevUrl,
next: nextUrl,
results: (result.results || []).map(userData => ({
id: userData._id.toString(),
username: userData.username,
address: userData.address,
storageUsed: Math.max(userData.storageUsed, 0),
quota: {
allowed: Number(userData.quota) || config.maxStorage * 1024 * 1024,
used: Math.max(Number(userData.storageUsed) || 0, 0)
},
activated: userData.setup
}))
};
res.json(response);
return next();
});
});
});
server.get({ name: 'addresses', path: '/addresses' }, (req, res, next) => {
res.charSet('utf-8');
const schema = Joi.object().keys({
query: Joi.string().empty('').max(255),
limit: Joi.number().default(20).min(1).max(250),
next: Joi.string().alphanum().max(100),
prev: Joi.string().alphanum().max(100),
page: Joi.number().default(1)
});
const result = Joi.validate(req.query, schema, {
abortEarly: false,
convert: true,
allowUnknown: true
});
if (result.error) {
res.json({
error: result.error.message
});
return next();
}
let query = result.value.query;
let limit = result.value.limit;
let page = result.value.page;
let pageNext = result.value.next;
let pagePrev = result.value.prev;
let filter = query
? {
address: {
$regex: query.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'),
$options: ''
}
}
: {};
db.users.collection('addresses').count(filter, (err, total) => {
if (err) {
res.json({
error: result.error.message
});
return next();
}
let opts = {
limit,
query: filter,
fields: {
_id: true,
address: true,
user: true
},
sortAscending: true
};
if (pageNext) {
opts.next = pageNext;
} else if (pagePrev) {
opts.prev = pagePrev;
}
MongoPaging.find(db.users.collection('addresses'), opts, (err, result) => {
if (err) {
res.json({
error: result.error.message
});
return next();
}
if (!result.hasPrevious) {
page = 1;
}
let prevUrl = result.hasPrevious
? server.router.render('addresses', {}, { prev: result.previous, limit, query: query || '', page: Math.max(page - 1, 1) })
: false;
let nextUrl = result.hasNext ? server.router.render('addresses', {}, { next: result.previous, limit, query: query || '', page: page + 1 }) : false;
let response = {
query,
total,
page,
prev: prevUrl,
next: nextUrl,
results: (result.results || []).map(addressData => ({
id: addressData._id.toString(),
address: addressData.address,
user: addressData.user.toString()
}))
};
res.json(response);
return next();
});
});
});
server.post('/users', (req, res, next) => {
res.charSet('utf-8');

View file

@ -6,7 +6,6 @@ let EventEmitter = require('events').EventEmitter;
// Expects that the folder listing is a Map
class MemoryNotifier extends EventEmitter {
constructor(options) {
super();
this.folders = options.folders || new Map();
@ -49,10 +48,10 @@ class MemoryNotifier extends EventEmitter {
* @param {Function} handler Function to run once there are new entries in the journal
*/
addListener(session, mailbox, handler) {
let eventName = this._eventName(session.user.username, mailbox);
let eventName = this._eventName(session.user.id, mailbox);
this._listeners.addListener(eventName, handler);
this.logger.debug('[%s] New journal listener for %s ("%s:%s")', session.id, eventName, session.user.username, mailbox);
this.logger.debug('[%s] New journal listener for %s ("%s:%s")', session.id, eventName, session.user.id, mailbox);
}
/**
@ -63,10 +62,10 @@ class MemoryNotifier extends EventEmitter {
* @param {Function} handler Function to run once there are new entries in the journal
*/
removeListener(session, mailbox, handler) {
let eventName = this._eventName(session.user.username, mailbox);
let eventName = this._eventName(session.user.id, mailbox);
this._listeners.removeListener(eventName, handler);
this.logger.debug('[%s] Removed journal listener from %s ("%s:%s")', session.id, eventName, session.user.username, mailbox);
this.logger.debug('[%s] Removed journal listener from %s ("%s:%s")', session.id, eventName, session.user.id, mailbox);
}
/**
@ -144,7 +143,6 @@ class MemoryNotifier extends EventEmitter {
return callback(null, folder.journal.slice(minIndex));
}
}
module.exports = MemoryNotifier;

View file

@ -131,6 +131,7 @@ module.exports = function(options) {
callback(null, {
user: {
id: 'id.' + login.username,
username: login.username
}
});
@ -306,7 +307,7 @@ module.exports = function(options) {
// do not write directly to stream, use notifications as the currently selected mailbox might not be the one that receives the message
this.notifier.addEntries(
session.user.username,
session.user.id,
mailbox,
{
command: 'EXISTS',
@ -404,7 +405,7 @@ module.exports = function(options) {
}
this.notifier.addEntries(
session.user.username,
session.user.id,
mailbox,
{
command: 'FETCH',
@ -456,7 +457,7 @@ module.exports = function(options) {
}
}
this.notifier.addEntries(session.user.username, mailbox, entries, () => {
this.notifier.addEntries(session.user.id, mailbox, entries, () => {
this.notifier.fire(session.user.id, mailbox);
return callback(null, true);
});
@ -502,8 +503,8 @@ module.exports = function(options) {
});
}
this.notifier.addEntries(update.destination, session.user.username, entries, () => {
this.notifier.fire(update.destination, session.user.username);
this.notifier.addEntries(update.destination, session.user.id, entries, () => {
this.notifier.fire(session.user.id, update.destination);
return callback(null, true, {
uidValidity: destinationFolder.uidValidity,
@ -544,7 +545,7 @@ module.exports = function(options) {
});
}
this.notifier.addEntries(session.user.username, mailbox, entries, () => {
this.notifier.addEntries(session.user.id, mailbox, entries, () => {
let pos = 0;
let processMessage = () => {
if (pos >= folder.messages.length) {

View file

@ -11,7 +11,7 @@
"license": "EUPL-1.1",
"devDependencies": {
"browserbox": "^0.9.1",
"chai": "^4.0.2",
"chai": "^4.1.0",
"eslint-config-nodemailer": "^1.2.0",
"grunt": "^1.0.1",
"grunt-cli": "^1.2.0",
@ -31,14 +31,15 @@
"libmime": "^3.1.0",
"libqp": "^1.1.0",
"mailsplit": "^4.0.2",
"mongo-cursor-pagination": "^5.0.0",
"mongodb": "^2.2.30",
"node-redis-scripty": "0.0.5",
"nodemailer": "^4.0.1",
"npmlog": "^4.1.2",
"qrcode": "^0.8.2",
"redfour": "^1.0.0",
"redfour": "^1.0.2",
"redis": "^2.7.1",
"restify": "^5.0.0",
"restify": "^5.0.1",
"seq-index": "^1.1.0",
"smtp-server": "^3.0.1",
"speakeasy": "^2.0.0",