This commit is contained in:
Andris Reinman 2018-11-23 20:57:45 +02:00
parent 95a7bb3b07
commit b8af48c13f
8 changed files with 210 additions and 27 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
define({ "name": "wildduck", "version": "1.0.0", "description": "WildDuck API docs", "title": "WildDuck API", "url": "https://api.wildduck.email", "sampleUrl": false, "defaultVersion": "0.0.0", "apidoc": "0.3.0", "generator": { "name": "apidoc", "time": "2018-11-14T11:21:41.763Z", "url": "http://apidocjs.com", "version": "0.17.6" } });
define({ "name": "wildduck", "version": "1.0.0", "description": "WildDuck API docs", "title": "WildDuck API", "url": "https://api.wildduck.email", "sampleUrl": false, "defaultVersion": "0.0.0", "apidoc": "0.3.0", "generator": { "name": "apidoc", "time": "2018-11-23T18:50:49.585Z", "url": "http://apidocjs.com", "version": "0.17.6" } });

View file

@ -1 +1 @@
{ "name": "wildduck", "version": "1.0.0", "description": "WildDuck API docs", "title": "WildDuck API", "url": "https://api.wildduck.email", "sampleUrl": false, "defaultVersion": "0.0.0", "apidoc": "0.3.0", "generator": { "name": "apidoc", "time": "2018-11-14T11:21:41.763Z", "url": "http://apidocjs.com", "version": "0.17.6" } }
{ "name": "wildduck", "version": "1.0.0", "description": "WildDuck API docs", "title": "WildDuck API", "url": "https://api.wildduck.email", "sampleUrl": false, "defaultVersion": "0.0.0", "apidoc": "0.3.0", "generator": { "name": "apidoc", "time": "2018-11-23T18:50:49.585Z", "url": "http://apidocjs.com", "version": "0.17.6" } }

View file

@ -1565,7 +1565,6 @@ module.exports = (db, server, userHandler) => {
* }
*
* @apiParam {String} id Users unique ID.
* @apiParam {String} [reason] Message to be shown to connected IMAP client
*
* @apiSuccess {Boolean} success Indicates successful response
* @apiSuccess {Number} storageUsed Calculated quota usage for the user
@ -1664,30 +1663,23 @@ module.exports = (db, server, userHandler) => {
// NB! Scattered query
storageData = await db.database
.collection('messages')
.aggregate(
[
{
$match: {
user
}
},
{
$group: {
_id: {
user: '$user'
},
storageUsed: {
$sum: '$size'
}
}
}
],
.aggregate([
{
cursor: {
batchSize: 1
$match: {
user
}
},
{
$group: {
_id: {
user: '$user'
},
storageUsed: {
$sum: '$size'
}
}
}
)
])
.toArray();
} catch (err) {
res.json({
@ -1742,6 +1734,86 @@ module.exports = (db, server, userHandler) => {
})
);
/**
* @api {post} /quota/reset Recalculate Quota for all Users
* @apiName PostUserQuotaAll
* @apiGroup Users
* @apiDescription This method recalculates quota usage for all Users. Normally not needed, only use it if quota numbers are way off.
* This method is not transactional, so if the user is currently receiving new messages then the resulting value is not exact.
* @apiHeader {String} X-Access-Token Optional access token if authentication is enabled
* @apiHeaderExample {json} Header-Example:
* {
* "X-Access-Token": "59fc66a03e54454869460e45"
* }
*
* @apiParam {String} id Users unique ID.
*
* @apiSuccess {Boolean} success Indicates successful response
*
* @apiError error Description of the error
*
* @apiExample {curl} Example usage:
* curl -i -XPOST http://localhost:8080/quota/reset \
* -H 'Content-type: application/json' \
* -d '{}'
*
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* {
* "success": true
* }
*
* @apiErrorExample {json} Error-Response:
* HTTP/1.1 200 OK
* {
* "error": "Failed to process request"
* }
*/
server.post(
'/quota/reset',
tools.asyncifyJson(async (req, res, next) => {
res.charSet('utf-8');
const schema = Joi.object().keys({
sess: Joi.string().max(255),
ip: Joi.string().ip({
version: ['ipv4', 'ipv6'],
cidr: 'forbidden'
})
});
const result = Joi.validate(req.params, schema, {
abortEarly: false,
convert: true
});
if (result.error) {
res.json({
error: result.error.message,
code: 'InputValidationError'
});
return next();
}
// permissions check
req.validate(roles.can(req.role).updateAny('users'));
let now = new Date();
await db.database.collection('tasks').insertOne({
task: 'quota',
locked: false,
lockedUntil: now,
created: now,
status: 'queued'
});
res.json({
success: true
});
return next();
})
);
/**
* @api {post} /users/:id/password/reset Reset password for an User
* @apiName ResetUserPassword

93
lib/tasks/quota.js Normal file
View file

@ -0,0 +1,93 @@
'use strict';
const log = require('npmlog');
const db = require('../db');
module.exports = (taskData, options, callback) => {
let cursor = db.users
.collection('users')
.find({})
.project({ _id: true, storageUsed: true });
let processNext = () => {
cursor.next((err, userData) => {
if (err) {
log.error('Tasks', 'task=quota id=%s error=%s', taskData._id, err.message);
return callback(err);
}
if (!userData) {
return cursor.close(() => callback(null, true));
}
db.database
.collection('messages')
.aggregate([
{
$match: {
user: userData._id
}
},
{
$group: {
_id: {
user: '$user'
},
storageUsed: {
$sum: '$size'
}
}
}
])
.toArray((err, storageData) => {
if (err) {
log.error('Tasks', 'task=quota id=%s user=%s error=%s', taskData._id, userData._id, err.message);
return setTimeout(processNext, 5000);
}
let storageUsed = (storageData && storageData[0] && storageData[0].storageUsed) || 0;
if (storageUsed === userData.storageUsed) {
log.info(
'Tasks',
'task=quota id=%s user=%s stored=%s calculated=%s updated=%s',
taskData._id,
userData._id,
userData.storageUsed,
storageUsed,
'no'
);
return setImmediate(processNext);
}
db.users.collection('users').updateOne(
{
_id: userData._id
},
{
$set: {
storageUsed: Number(storageUsed) || 0
}
},
(err, r) => {
if (err) {
log.error('Tasks', 'task=quota id=%s user=%s error=%s', taskData._id, userData._id, err.message);
return setTimeout(processNext, 5000);
}
log.info(
'Tasks',
'task=quota id=%s user=%s stored=%s calculated=%s updated=%s',
taskData._id,
userData._id,
userData.storageUsed,
storageUsed,
r.modifiedCount ? 'yes' : 'no'
);
return setImmediate(processNext);
}
);
});
});
};
processNext();
};

View file

@ -1,6 +1,6 @@
{
"name": "wildduck",
"version": "1.10.13",
"version": "1.10.14",
"description": "IMAP/POP3 server built with Node.js and MongoDB",
"main": "server.js",
"scripts": {

View file

@ -11,6 +11,8 @@ const MessageHandler = require('./lib/message-handler');
const setupIndexes = yaml.safeLoad(fs.readFileSync(__dirname + '/indexes.yaml', 'utf8'));
const taskRestore = require('./lib/tasks/restore');
const taskUserDelete = require('./lib/tasks/user-delete');
const taskQuota = require('./lib/tasks/quota');
let messageHandler;
let gcTimeout;
@ -462,6 +464,22 @@ function processTask(taskData, callback) {
callback(null, true);
}
);
case 'user-delete':
return taskUserDelete(taskData, {}, err => {
if (err) {
return callback(err);
}
// release
callback(null, true);
});
case 'quota':
return taskQuota(taskData, {}, err => {
if (err) {
return callback(err);
}
// release
callback(null, true);
});
default:
// release task by returning true
return callback(null, true);