mirror of
https://github.com/nodemailer/wildduck.git
synced 2024-11-10 09:32:28 +08:00
added API endpoints for listing usernames and addresses
This commit is contained in:
parent
cb745030d4
commit
cf20ada049
4 changed files with 243 additions and 16 deletions
229
api.js
229
api.js
|
@ -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');
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in a new issue