mirror of
https://github.com/wassname/talk.git
synced 2026-07-05 08:49:04 +08:00
Merge branch 'master' into better-graph
This commit is contained in:
@@ -14,6 +14,7 @@ client/coral-framework/graphql/introspection.json
|
||||
*.swp
|
||||
*.DS_STORE
|
||||
.prettierrc.json
|
||||
.vscode
|
||||
|
||||
coverage/
|
||||
test/e2e/reports/
|
||||
|
||||
+20
-75
@@ -7,8 +7,6 @@
|
||||
const util = require('./util');
|
||||
const program = require('commander');
|
||||
const inquirer = require('inquirer');
|
||||
const { graphql } = require('graphql');
|
||||
const helpers = require('../services/migration/helpers');
|
||||
const { stripIndent } = require('common-tags');
|
||||
const Table = require('cli-table');
|
||||
|
||||
@@ -21,31 +19,15 @@ inquirer.registerPrompt(
|
||||
require('inquirer-autocomplete-prompt')
|
||||
);
|
||||
|
||||
const schema = require('../graph/schema');
|
||||
const Context = require('../graph/context');
|
||||
const UsersService = require('../services/users');
|
||||
const UserModel = require('../models/user');
|
||||
const CommentModel = require('../models/comment');
|
||||
const ActionModel = require('../models/action');
|
||||
const USER_ROLES = require('../models/enum/user_roles');
|
||||
const mongoose = require('../services/mongoose');
|
||||
|
||||
// Register the shutdown criteria.
|
||||
util.onshutdown([() => mongoose.disconnect()]);
|
||||
|
||||
/**
|
||||
* transforms a specific action to a removal action on the target model.
|
||||
*/
|
||||
const actionDecrTransformer = ({ item_id, action_type, group_id }) => ({
|
||||
query: { id: item_id },
|
||||
update: {
|
||||
$inc: {
|
||||
[`action_counts.${action_type.toLowerCase()}`]: -1,
|
||||
[`action_counts.${action_type.toLowerCase()}_${group_id.toLowerCase()}`]: -1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Deletes a user and cleans up their associated verifications.
|
||||
*/
|
||||
@@ -76,66 +58,29 @@ async function deleteUser(userID) {
|
||||
return util.shutdown();
|
||||
}
|
||||
|
||||
const { transformSingleWithCursor } = helpers({
|
||||
queryBatchSize: 10000,
|
||||
updateBatchSize: 10000,
|
||||
});
|
||||
const ctx = Context.forSystem();
|
||||
|
||||
console.warn("Removing user's actions");
|
||||
|
||||
// Remove all actions against comments.
|
||||
await transformSingleWithCursor(
|
||||
ActionModel.collection.find({ user_id: user.id, item_type: 'COMMENTS' }),
|
||||
actionDecrTransformer,
|
||||
CommentModel
|
||||
const { data, errors } = await ctx.graphql(
|
||||
`
|
||||
mutation DeleteUser($user_id: ID!) {
|
||||
delUser(id: $user_id) {
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ user_id: user.id }
|
||||
);
|
||||
if (errors) {
|
||||
throw errors;
|
||||
}
|
||||
|
||||
// Remove all actions against users.
|
||||
await transformSingleWithCursor(
|
||||
ActionModel.collection.find({ user_id: user.id, item_type: 'USERS' }),
|
||||
actionDecrTransformer,
|
||||
UserModel
|
||||
);
|
||||
if (data.errors) {
|
||||
throw data.errors;
|
||||
}
|
||||
|
||||
// Remove all the user's actions.
|
||||
await ActionModel.where({ user_id: user.id })
|
||||
.setOptions({ multi: true })
|
||||
.remove();
|
||||
|
||||
console.warn("Removing user's comments");
|
||||
|
||||
// Removes all the user's reply counts on each of the comments that they
|
||||
// have commented on.
|
||||
await transformSingleWithCursor(
|
||||
CommentModel.collection.aggregate([
|
||||
{ $match: { author_id: user.id } },
|
||||
{
|
||||
$group: {
|
||||
_id: '$parent_id',
|
||||
count: { $sum: 1 },
|
||||
},
|
||||
},
|
||||
]),
|
||||
({ _id: parent_id, count }) => ({
|
||||
query: { id: parent_id },
|
||||
update: {
|
||||
$inc: {
|
||||
reply_count: -1 * count,
|
||||
},
|
||||
},
|
||||
}),
|
||||
CommentModel
|
||||
);
|
||||
|
||||
// Remove all the user's comments.
|
||||
await CommentModel.where({ author_id: user.id })
|
||||
.setOptions({ multi: true })
|
||||
.remove();
|
||||
|
||||
console.warn('Removing the user');
|
||||
|
||||
// Remove the user.
|
||||
await user.remove();
|
||||
console.log('User was deleted.');
|
||||
|
||||
util.shutdown();
|
||||
} catch (err) {
|
||||
@@ -197,7 +142,7 @@ async function searchUsers() {
|
||||
value = '';
|
||||
}
|
||||
|
||||
const { data, errors } = await graphql(schema, searchQuery, {}, ctx, {
|
||||
const { data, errors } = await ctx.graphql(searchQuery, {
|
||||
value,
|
||||
});
|
||||
if (errors && errors.length > 0) {
|
||||
|
||||
@@ -75,7 +75,8 @@ class UserDetailComment extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.story}>
|
||||
Story: {comment.asset.title}
|
||||
{t('common.story')}:{' '}
|
||||
{comment.asset.title ? comment.asset.title : comment.asset.url}
|
||||
{
|
||||
<Link to={`/admin/moderate/${comment.asset.id}`}>
|
||||
{t('modqueue.moderate')}
|
||||
@@ -106,6 +107,7 @@ class UserDetailComment extends React.Component {
|
||||
<div className={styles.sideActions}>
|
||||
<IfHasLink text={comment.body}>
|
||||
<span className={styles.hasLinks}>
|
||||
{/* TODO: translate string */}
|
||||
<Icon name="error_outline" /> Contains Link
|
||||
</span>
|
||||
</IfHasLink>
|
||||
|
||||
@@ -125,7 +125,8 @@ class Comment extends React.Component {
|
||||
</div>
|
||||
|
||||
<div className={styles.moderateArticle}>
|
||||
Story: {comment.asset.title}
|
||||
{t('common.story')}:{' '}
|
||||
{comment.asset.title ? comment.asset.title : comment.asset.url}
|
||||
{!currentAsset && (
|
||||
<Link to={`/admin/moderate/${comment.asset.id}`}>
|
||||
{t('modqueue.moderate')}
|
||||
@@ -156,6 +157,7 @@ class Comment extends React.Component {
|
||||
<div className={styles.sideActions}>
|
||||
<IfHasLink text={comment.body}>
|
||||
<span className={styles.hasLinks}>
|
||||
{/* TODO: translate string */}
|
||||
<Icon name="error_outline" /> Contains Link
|
||||
</span>
|
||||
</IfHasLink>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const errors = require('../../errors');
|
||||
const UsersService = require('../../services/users');
|
||||
const migrationHelpers = require('../../services/migration/helpers');
|
||||
const {
|
||||
CHANGE_USERNAME,
|
||||
SET_USERNAME,
|
||||
@@ -7,6 +8,7 @@ const {
|
||||
SET_USER_BAN_STATUS,
|
||||
SET_USER_SUSPENSION_STATUS,
|
||||
UPDATE_USER_ROLES,
|
||||
DELETE_USER,
|
||||
} = require('../../perms/constants');
|
||||
|
||||
const setUserUsernameStatus = async (ctx, id, status) => {
|
||||
@@ -70,6 +72,87 @@ const setRole = (ctx, id, role) => {
|
||||
return UsersService.setRole(id, role);
|
||||
};
|
||||
|
||||
/**
|
||||
* transforms a specific action to a removal action on the target model.
|
||||
*/
|
||||
const actionDecrTransformer = ({ item_id, action_type, group_id }) => ({
|
||||
query: { id: item_id },
|
||||
update: {
|
||||
$inc: {
|
||||
[`action_counts.${action_type.toLowerCase()}`]: -1,
|
||||
[`action_counts.${action_type.toLowerCase()}_${group_id.toLowerCase()}`]: -1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// delUser will delete a given user with the specified id.
|
||||
const delUser = async (ctx, id) => {
|
||||
const { connectors: { models: { User, Action, Comment } } } = ctx;
|
||||
|
||||
// Find the user we're removing.
|
||||
const user = await User.findOne({ id });
|
||||
if (!user) {
|
||||
throw errors.ErrNotFound;
|
||||
}
|
||||
|
||||
// Get the query transformer we'll use to help batch process the user
|
||||
// deletion.
|
||||
const { transformSingleWithCursor } = migrationHelpers({
|
||||
queryBatchSize: 10000,
|
||||
updateBatchSize: 10000,
|
||||
});
|
||||
|
||||
// Remove all actions against comments.
|
||||
await transformSingleWithCursor(
|
||||
Action.collection.find({ user_id: user.id, item_type: 'COMMENTS' }),
|
||||
actionDecrTransformer,
|
||||
Comment
|
||||
);
|
||||
|
||||
// Remove all actions against users.
|
||||
await transformSingleWithCursor(
|
||||
Action.collection.find({ user_id: user.id, item_type: 'USERS' }),
|
||||
actionDecrTransformer,
|
||||
User
|
||||
);
|
||||
|
||||
// Remove all the user's actions.
|
||||
await Action.where({ user_id: user.id })
|
||||
.setOptions({ multi: true })
|
||||
.remove();
|
||||
|
||||
// Removes all the user's reply counts on each of the comments that they
|
||||
// have commented on.
|
||||
await transformSingleWithCursor(
|
||||
Comment.collection.aggregate([
|
||||
{ $match: { author_id: user.id } },
|
||||
{
|
||||
$group: {
|
||||
_id: '$parent_id',
|
||||
count: { $sum: 1 },
|
||||
},
|
||||
},
|
||||
]),
|
||||
({ _id: parent_id, count }) => ({
|
||||
query: { id: parent_id },
|
||||
update: {
|
||||
$inc: {
|
||||
reply_count: -1 * count,
|
||||
},
|
||||
},
|
||||
}),
|
||||
Comment
|
||||
);
|
||||
|
||||
// Remove all the user's comments.
|
||||
await Comment.where({ author_id: user.id })
|
||||
.setOptions({ multi: true })
|
||||
.remove();
|
||||
|
||||
// Remove the user.
|
||||
await user.remove();
|
||||
};
|
||||
|
||||
module.exports = ctx => {
|
||||
let mutators = {
|
||||
User: {
|
||||
@@ -81,6 +164,7 @@ module.exports = ctx => {
|
||||
setUserUsernameStatus: () => Promise.reject(errors.ErrNotAuthorized),
|
||||
setUsername: () => Promise.reject(errors.ErrNotAuthorized),
|
||||
stopIgnoringUser: () => Promise.reject(errors.ErrNotAuthorized),
|
||||
del: () => Promise.reject(errors.ErrNotAuthorized),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -116,6 +200,10 @@ module.exports = ctx => {
|
||||
mutators.User.setUserSuspensionStatus = (id, until, message) =>
|
||||
setUserSuspensionStatus(ctx, id, until, message);
|
||||
}
|
||||
|
||||
if (ctx.user.can(DELETE_USER)) {
|
||||
mutators.User.del = id => delUser(ctx, id);
|
||||
}
|
||||
}
|
||||
|
||||
return mutators;
|
||||
|
||||
@@ -136,6 +136,9 @@ const RootMutation = {
|
||||
forceScrapeAsset: async (_, { id }, { mutators: { Asset } }) => {
|
||||
await Asset.scrape(id);
|
||||
},
|
||||
delUser: async (_, { id }, { mutators: { User } }) => {
|
||||
await User.del(id);
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = RootMutation;
|
||||
|
||||
@@ -1426,6 +1426,12 @@ type ForceScrapeAssetResponse implements Response {
|
||||
errors: [UserError!]
|
||||
}
|
||||
|
||||
type DelUserResponse implements Response {
|
||||
|
||||
# An array of errors relating to the mutation that occurred.
|
||||
errors: [UserError!]
|
||||
}
|
||||
|
||||
# All mutations for the application are defined on this object.
|
||||
type RootMutation {
|
||||
|
||||
@@ -1523,6 +1529,9 @@ type RootMutation {
|
||||
|
||||
# forceScrapeAsset will force scrape the Asset with the given ID.
|
||||
forceScrapeAsset(id: ID!): ForceScrapeAssetResponse
|
||||
|
||||
# delUser will delete the user with the specified id.
|
||||
delUser(id: ID!): DelUserResponse
|
||||
}
|
||||
|
||||
type UsernameChangedPayload {
|
||||
|
||||
@@ -18,4 +18,5 @@ module.exports = {
|
||||
UPDATE_ASSET_SETTINGS: 'UPDATE_ASSET_SETTINGS',
|
||||
UPDATE_ASSET_STATUS: 'UPDATE_ASSET_STATUS',
|
||||
UPDATE_SETTINGS: 'UPDATE_SETTINGS',
|
||||
DELETE_USER: 'DELETE_USER',
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ const {
|
||||
HandleGenerateCredentials,
|
||||
HandleLogout,
|
||||
} = require('../../../services/passport');
|
||||
const authz = require('../../../middleware/authorization');
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
@@ -26,7 +27,7 @@ router.get('/', (req, res, next) => {
|
||||
/**
|
||||
* This blacklists the token used to authenticate.
|
||||
*/
|
||||
router.delete('/', HandleLogout);
|
||||
router.delete('/', authz.needed(), HandleLogout);
|
||||
|
||||
//==============================================================================
|
||||
// PASSPORT ROUTES
|
||||
|
||||
+13
-13
@@ -184,24 +184,24 @@ async function ValidateUserLogin(loginProfile, user, done) {
|
||||
* Revoke the token on the request.
|
||||
*/
|
||||
const HandleLogout = async (req, res, next) => {
|
||||
const { jwt } = req;
|
||||
|
||||
const now = new Date();
|
||||
const expiry = (jwt.exp - now.getTime() / 1000).toFixed(0);
|
||||
|
||||
try {
|
||||
const { jwt } = req;
|
||||
|
||||
const now = new Date();
|
||||
const expiry = (jwt.exp - now.getTime() / 1000).toFixed(0);
|
||||
|
||||
await client().set(`jtir[${jwt.jti}]`, now.toISOString(), 'EX', expiry);
|
||||
|
||||
// Only clear the cookie on logout if enabled.
|
||||
if (JWT_CLEAR_COOKIE_LOGOUT) {
|
||||
debug('clearing the login cookie');
|
||||
res.clearCookie(JWT_SIGNING_COOKIE_NAME);
|
||||
}
|
||||
|
||||
res.status(204).end();
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
// Only clear the cookie on logout if enabled.
|
||||
if (JWT_CLEAR_COOKIE_LOGOUT) {
|
||||
debug('clearing the login cookie');
|
||||
res.clearCookie(JWT_SIGNING_COOKIE_NAME);
|
||||
}
|
||||
|
||||
res.status(204).end();
|
||||
};
|
||||
|
||||
const checkGeneralTokenBlacklist = jwt =>
|
||||
|
||||
Reference in New Issue
Block a user