Merge branch 'develop' into bootstrap5

This commit is contained in:
Barış Soner Uşaklı
2022-08-29 14:55:06 -04:00
197 changed files with 981 additions and 432 deletions

View File

@@ -3,8 +3,9 @@
const cacheController = module.exports;
const utils = require('../../utils');
const plugins = require('../../plugins');
cacheController.get = function (req, res) {
cacheController.get = async function (req, res) {
const postCache = require('../../posts/cache');
const groupCache = require('../../groups').cache;
const { objectCache } = require('../../database');
@@ -23,29 +24,33 @@ cacheController.get = function (req, res) {
misses: utils.addCommas(String(cache.misses)),
hitRatio: ((cache.hits / (cache.hits + cache.misses) || 0)).toFixed(4),
enabled: cache.enabled,
ttl: cache.ttl,
};
}
const data = {
postCache: getInfo(postCache),
groupCache: getInfo(groupCache),
localCache: getInfo(localCache),
let caches = {
post: postCache,
group: groupCache,
local: localCache,
};
if (objectCache) {
data.objectCache = getInfo(objectCache);
caches.object = objectCache;
}
caches = await plugins.hooks.fire('filter:admin.cache.get', caches);
for (const [key, value] of Object.entries(caches)) {
caches[key] = getInfo(value);
}
res.render('admin/advanced/cache', data);
res.render('admin/advanced/cache', { caches });
};
cacheController.dump = function (req, res, next) {
const caches = {
cacheController.dump = async function (req, res, next) {
let caches = {
post: require('../../posts/cache'),
object: require('../../database').objectCache,
group: require('../../groups').cache,
local: require('../../cache'),
};
caches = await plugins.hooks.fire('filter:admin.cache.get', caches);
if (!caches[req.query.name]) {
return next();
}

View File

@@ -9,6 +9,7 @@ const groups = require('../../groups');
const meta = require('../../meta');
const pagination = require('../../pagination');
const events = require('../../events');
const slugify = require('../../slugify');
const groupsController = module.exports;
@@ -31,7 +32,8 @@ groupsController.list = async function (req, res) {
};
groupsController.get = async function (req, res, next) {
const groupName = req.params.name;
const slug = slugify(req.params.name);
const groupName = await groups.getGroupNameByGroupSlug(slug);
const [groupNames, group] = await Promise.all([
getGroupNames(),
groups.get(groupName, { uid: req.uid, truncateUserList: true, userListCount: 20 }),

View File

@@ -339,19 +339,7 @@ authenticationController.doLogin = async function (req, uid) {
return;
}
const loginAsync = util.promisify(req.login).bind(req);
const { reroll } = req.res.locals;
if (reroll !== false) {
const regenerateSession = util.promisify(req.session.regenerate).bind(req.session);
const sessionData = { ...req.session };
await regenerateSession();
for (const [prop, value] of Object.entries(sessionData)) {
req.session[prop] = value;
}
}
await loginAsync({ uid: uid }, { keepSessionInfo: true });
await loginAsync({ uid: uid }, { keepSessionInfo: req.res.locals !== false });
await authenticationController.onSuccessfulLogin(req, uid);
};

View File

@@ -434,11 +434,22 @@ helpers.formatApiResponse = async (statusCode, res, payload) => {
res.set('cache-control', 'private');
}
let code = 'ok';
let message = 'OK';
switch (statusCode) {
case 202:
code = 'accepted';
message = 'Accepted';
break;
case 204:
code = 'no-content';
message = 'No Content';
break;
}
res.status(statusCode).json({
status: {
code: 'ok',
message: 'OK',
},
status: { code, message },
response: payload || {},
});
} else if (payload instanceof Error) {

View File

@@ -88,6 +88,7 @@ topicsController.get = async function getTopic(req, res, next) {
topicData['reputation:disabled'] = meta.config['reputation:disabled'];
topicData['downvote:disabled'] = meta.config['downvote:disabled'];
topicData['feeds:disableRSS'] = meta.config['feeds:disableRSS'] || 0;
topicData['signatures:hideDuplicates'] = meta.config['signatures:hideDuplicates'];
topicData.bookmarkThreshold = meta.config.bookmarkThreshold;
topicData.necroThreshold = meta.config.necroThreshold;
topicData.postEditDuration = meta.config.postEditDuration;

View File

@@ -1,6 +1,7 @@
'use strict';
const path = require('path');
const winston = require('winston');
const user = require('../user');
const privileges = require('../privileges');
@@ -90,7 +91,10 @@ userController.exportProfile = async function (req, res, next) {
sendExport(`${res.locals.uid}_profile.json`, 'application/json', res, next);
};
// DEPRECATED; Remove in NodeBB v3.0.0
function sendExport(filename, type, res, next) {
winston.warn(`[users/export] Access via page API is deprecated, use GET /api/v3/users/:uid/exports/:type instead.`);
res.sendFile(filename, {
root: path.join(__dirname, '../../build/export'),
headers: {

View File

@@ -2,6 +2,9 @@
const util = require('util');
const nconf = require('nconf');
const path = require('path');
const crypto = require('crypto');
const fs = require('fs').promises;
const db = require('../../database');
const api = require('../../api');
@@ -15,6 +18,12 @@ const helpers = require('../helpers');
const Users = module.exports;
const exportMetadata = new Map([
['posts', ['csv', 'text/csv']],
['uploads', ['zip', 'application/zip']],
['profile', ['json', 'application/json']],
]);
const hasAdminPrivilege = async (uid, privilege) => {
const ok = await privileges.admin.can(`admin:${privilege}`, uid);
if (!ok) {
@@ -296,3 +305,52 @@ Users.confirmEmail = async (req, res) => {
helpers.formatApiResponse(404, res);
}
};
const prepareExport = async (req, res) => {
const [extension] = exportMetadata.get(req.params.type);
const filename = `${req.params.uid}_${req.params.type}.${extension}`;
try {
const stat = await fs.stat(path.join(__dirname, '../../../build/export', filename));
const modified = new Date(stat.mtimeMs);
res.set('Last-Modified', modified.toUTCString());
res.set('ETag', `"${crypto.createHash('md5').update(String(stat.mtimeMs)).digest('hex')}"`);
res.status(204);
return true;
} catch (e) {
res.status(404);
return false;
}
};
Users.checkExportByType = async (req, res) => {
await prepareExport(req, res);
res.end();
};
Users.getExportByType = async (req, res) => {
const [extension, mime] = exportMetadata.get(req.params.type);
const filename = `${req.params.uid}_${req.params.type}.${extension}`;
const exists = await prepareExport(req, res);
if (!exists) {
return res.end();
}
res.status(200);
res.sendFile(filename, {
root: path.join(__dirname, '../../../build/export'),
headers: {
'Content-Type': mime,
'Content-Disposition': `attachment; filename=${filename}`,
},
}, (err) => {
if (err) {
throw err;
}
});
};
Users.generateExportsByType = async (req, res) => {
await api.users.generateExport(req, req.params);
helpers.formatApiResponse(202, res);
};