mirror of
https://github.com/wassname/talk.git
synced 2026-08-11 11:27:10 +08:00
Merge branch 'master' into user-json
This commit is contained in:
@@ -14,6 +14,7 @@ const AssetsService = require('../services/assets');
|
||||
const mongoose = require('../services/mongoose');
|
||||
const scraper = require('../services/scraper');
|
||||
const inquirer = require('inquirer');
|
||||
const { URL } = require('url');
|
||||
|
||||
// Register the shutdown criteria.
|
||||
util.onshutdown([() => mongoose.disconnect()]);
|
||||
@@ -125,6 +126,70 @@ async function merge(srcID, dstID) {
|
||||
}
|
||||
}
|
||||
|
||||
async function rewrite(search, replace, options) {
|
||||
try {
|
||||
search = new RegExp(search);
|
||||
|
||||
const assets = await AssetModel.find({
|
||||
url: { $regex: search },
|
||||
});
|
||||
if (assets.length === 0) {
|
||||
console.log(`No assets found with the pattern: ${search}`);
|
||||
return util.shutdown(0);
|
||||
}
|
||||
|
||||
let opts = [];
|
||||
assets.forEach(({ id, url: oldURL }) => {
|
||||
// Replace the url.
|
||||
const newURL = oldURL.replace(search, replace);
|
||||
|
||||
// Try to validate that the new url is valid.
|
||||
try {
|
||||
new URL(newURL);
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`Rewrite would have replaced the valid URL ${oldURL} with an invalid one ${newURL}`
|
||||
);
|
||||
}
|
||||
|
||||
opts.push({
|
||||
find: { id },
|
||||
updateOne: { $set: { url: newURL } },
|
||||
id,
|
||||
oldURL,
|
||||
newURL,
|
||||
});
|
||||
});
|
||||
|
||||
if (opts.length > 0) {
|
||||
if (options.dryRun) {
|
||||
const table = new Table({ head: ['ID', 'Old URL', 'New URL'] });
|
||||
|
||||
opts.forEach(({ id, oldURL, newURL }) => {
|
||||
table.push([id, oldURL, newURL]);
|
||||
});
|
||||
|
||||
console.log(table.toString());
|
||||
} else {
|
||||
const bulk = AssetModel.collection.initializeUnorderedBulkOp();
|
||||
opts.forEach(({ find, updateOne, oldURL, newURL }) => {
|
||||
// If the url was updated with the operation, then queue up the update op.
|
||||
if (newURL !== oldURL) {
|
||||
bulk.find(find).updateOne(updateOne);
|
||||
}
|
||||
});
|
||||
await bulk.execute();
|
||||
console.log(`${opts.length} assets had their url's updated`);
|
||||
}
|
||||
}
|
||||
|
||||
util.shutdown(0);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
util.shutdown(1);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// Setting up the program command line arguments.
|
||||
//==============================================================================
|
||||
@@ -151,6 +216,14 @@ program
|
||||
)
|
||||
.action(merge);
|
||||
|
||||
program
|
||||
.command('rewrite <search> <replace>')
|
||||
.option('-d, --dry-run', 'enables dry run of the replacement')
|
||||
.description(
|
||||
"rewrites asset url's using the provided regex replacement pattern"
|
||||
)
|
||||
.action(rewrite);
|
||||
|
||||
program.parse(process.argv);
|
||||
|
||||
// If there is no command listed, output help.
|
||||
|
||||
@@ -3,15 +3,15 @@ import { gql, compose } from 'react-apollo';
|
||||
import { withFragments } from 'coral-framework/hocs';
|
||||
import AssetStatusInfo from '../components/AssetStatusInfo';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withUpdateAssetStatus } from 'coral-framework/graphql/mutations';
|
||||
import {
|
||||
withUpdateAssetStatus,
|
||||
withCloseAsset,
|
||||
} from 'coral-framework/graphql/mutations';
|
||||
|
||||
class AssetStatusInfoContainer extends React.Component {
|
||||
openAsset = () =>
|
||||
this.props.updateAssetStatus(this.props.asset.id, { closedAt: null });
|
||||
closeAsset = () =>
|
||||
this.props.updateAssetStatus(this.props.asset.id, {
|
||||
closedAt: new Date().toISOString(),
|
||||
});
|
||||
closeAsset = () => this.props.closeAsset(this.props.asset.id);
|
||||
|
||||
render() {
|
||||
return (
|
||||
@@ -29,6 +29,7 @@ class AssetStatusInfoContainer extends React.Component {
|
||||
AssetStatusInfoContainer.propTypes = {
|
||||
asset: PropTypes.object.isRequired,
|
||||
updateAssetStatus: PropTypes.func.isRequired,
|
||||
closeAsset: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const withAssetStatusInfoFragments = withFragments({
|
||||
@@ -41,6 +42,10 @@ const withAssetStatusInfoFragments = withFragments({
|
||||
`,
|
||||
});
|
||||
|
||||
const enhance = compose(withAssetStatusInfoFragments, withUpdateAssetStatus);
|
||||
const enhance = compose(
|
||||
withAssetStatusInfoFragments,
|
||||
withUpdateAssetStatus,
|
||||
withCloseAsset
|
||||
);
|
||||
|
||||
export default enhance(AssetStatusInfoContainer);
|
||||
|
||||
@@ -3,27 +3,28 @@ import { createDefaultResponseFragments } from '../utils';
|
||||
// fragments defined here are automatically registered.
|
||||
export default {
|
||||
...createDefaultResponseFragments(
|
||||
'SetUserRoleResponse',
|
||||
'ChangeUsernameResponse',
|
||||
'SetUsernameResponse',
|
||||
'BanUsersResponse',
|
||||
'UnbanUserResponse',
|
||||
'SetUserSuspensionStatusResponse',
|
||||
'SetCommentStatusResponse',
|
||||
'SetUsernameStatusResponse',
|
||||
'UnsuspendUserResponse',
|
||||
'SuspendUserResponse',
|
||||
'ChangeUsernameResponse',
|
||||
'CloseAssetResponse',
|
||||
'CreateCommentResponse',
|
||||
'CreateFlagResponse',
|
||||
'EditCommentResponse',
|
||||
'PostFlagResponse',
|
||||
'CreateDontAgreeResponse',
|
||||
'CreateFlagResponse',
|
||||
'DeleteActionResponse',
|
||||
'ModifyTagResponse',
|
||||
'EditCommentResponse',
|
||||
'IgnoreUserResponse',
|
||||
'ModifyTagResponse',
|
||||
'PostFlagResponse',
|
||||
'SetCommentStatusResponse',
|
||||
'SetUsernameResponse',
|
||||
'SetUsernameStatusResponse',
|
||||
'SetUserRoleResponse',
|
||||
'SetUserSuspensionStatusResponse',
|
||||
'StopIgnoringUserResponse',
|
||||
'UpdateSettingsResponse',
|
||||
'SuspendUserResponse',
|
||||
'UnbanUserResponse',
|
||||
'UnsuspendUserResponse',
|
||||
'UpdateAssetSettingsResponse',
|
||||
'UpdateAssetStatusResponse'
|
||||
'UpdateAssetStatusResponse',
|
||||
'UpdateSettingsResponse'
|
||||
),
|
||||
};
|
||||
|
||||
@@ -665,3 +665,46 @@ export const withUpdateAssetStatus = withMutation(
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
export const withCloseAsset = withMutation(
|
||||
gql`
|
||||
mutation CloseAsset($id: ID!) {
|
||||
closeAsset(id: $id) {
|
||||
...CloseAssetResponse
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
props: ({ mutate }) => ({
|
||||
closeAsset: id => {
|
||||
return mutate({
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
optimisticResponse: {
|
||||
closeAsset: {
|
||||
__typename: 'CloseAssetResponse',
|
||||
errors: null,
|
||||
},
|
||||
},
|
||||
update: proxy => {
|
||||
const fragment = gql`
|
||||
fragment Talk_CloseAssetResponse on Asset {
|
||||
closedAt
|
||||
isClosed
|
||||
}
|
||||
`;
|
||||
|
||||
const fragmentId = `Asset_${id}`;
|
||||
const data = {
|
||||
__typename: 'Asset',
|
||||
closedAt: new Date(),
|
||||
isClosed: true,
|
||||
};
|
||||
proxy.writeFragment({ fragment, id: fragmentId, data });
|
||||
},
|
||||
});
|
||||
},
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -9,6 +9,8 @@ import 'moment/locale/es';
|
||||
import 'moment/locale/fr';
|
||||
import 'moment/locale/pt-br';
|
||||
|
||||
import { createStorage } from 'coral-framework/services/storage';
|
||||
|
||||
import daTA from 'timeago.js/locales/da';
|
||||
import esTA from 'timeago.js/locales/es';
|
||||
import frTA from 'timeago.js/locales/fr';
|
||||
@@ -38,25 +40,35 @@ const translations = {
|
||||
let lang;
|
||||
let timeagoInstance;
|
||||
|
||||
function setLocale(locale) {
|
||||
function setLocale(storage, locale) {
|
||||
try {
|
||||
localStorage.setItem('locale', locale);
|
||||
if (storage) {
|
||||
storage.setItem('locale', locale);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
function getLocale() {
|
||||
return (
|
||||
localStorage.getItem('locale') ||
|
||||
navigator.language ||
|
||||
defaultLanguage
|
||||
).split('-')[0];
|
||||
function getLocale(storage) {
|
||||
try {
|
||||
return (
|
||||
(storage && storage.getItem('locale')) ||
|
||||
navigator.language ||
|
||||
defaultLanguage
|
||||
).split('-')[0];
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
const locale = getLocale();
|
||||
setLocale(locale);
|
||||
export function setupTranslations() {
|
||||
// Setup the translation framework with the storage.
|
||||
const storage = createStorage();
|
||||
|
||||
const locale = getLocale(storage);
|
||||
setLocale(storage, locale);
|
||||
|
||||
// Setting moment
|
||||
moment.locale(locale);
|
||||
@@ -114,4 +126,5 @@ export function t(key, ...replacements) {
|
||||
|
||||
export default t;
|
||||
|
||||
init();
|
||||
// Setup the translations globally as soon as this module runs.
|
||||
setupTranslations();
|
||||
|
||||
@@ -38,11 +38,30 @@ const updateStatus = async (ctx, id, { closedAt, closedMessage }) =>
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* closeNow will close an asset for commenting.
|
||||
*
|
||||
* @param {Object} ctx graphql context
|
||||
* @param {String} id the asset's id to close
|
||||
*/
|
||||
const closeNow = async (ctx, id) =>
|
||||
AssetModel.update(
|
||||
{
|
||||
id,
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
closedAt: new Date(),
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
module.exports = ctx => {
|
||||
let mutators = {
|
||||
Asset: {
|
||||
updateSettings: () => Promise.reject(errors.ErrNotAuthorized),
|
||||
updateStatus: () => Promise.reject(errors.ErrNotAuthorized),
|
||||
closeNow: () => Promise.reject(errors.ErrNotAuthorized),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -55,6 +74,7 @@ module.exports = ctx => {
|
||||
if (ctx.user.can(UPDATE_ASSET_STATUS)) {
|
||||
mutators.Asset.updateStatus = (id, status) =>
|
||||
updateStatus(ctx, id, status);
|
||||
mutators.Asset.closeNow = id => closeNow(ctx, id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -94,6 +94,9 @@ const RootMutation = {
|
||||
) => {
|
||||
await Asset.updateStatus(id, status);
|
||||
},
|
||||
closeAsset: async (_, { id }, { mutators: { Asset } }) => {
|
||||
await Asset.closeNow(id);
|
||||
},
|
||||
setUserRole: async (_, { id, role }, { mutators: { User } }) => {
|
||||
await User.setRole(id, role);
|
||||
},
|
||||
|
||||
@@ -1111,6 +1111,14 @@ type UpdateAssetStatusResponse implements Response {
|
||||
errors: [UserError!]
|
||||
}
|
||||
|
||||
# CloseAssetResponse is the response returned with possibly some errors
|
||||
# relating to the update status attempt.
|
||||
type CloseAssetResponse implements Response {
|
||||
|
||||
# An array of errors relating to the mutation that occurred.
|
||||
errors: [UserError!]
|
||||
}
|
||||
|
||||
# UpdateAssetSettingsResponse is the response returned with possibly some errors
|
||||
# relating to the update settings attempt.
|
||||
type UpdateAssetSettingsResponse implements Response {
|
||||
@@ -1446,6 +1454,9 @@ type RootMutation {
|
||||
# Mutation is restricted.
|
||||
updateAssetStatus(id: ID!, input: UpdateAssetStatusInput!): UpdateAssetStatusResponse
|
||||
|
||||
# closeAsset will close the asset for commenting based on server time.
|
||||
closeAsset(id: ID!): CloseAssetResponse
|
||||
|
||||
# updateSettings will update the global settings.
|
||||
# Mutation is restricted.
|
||||
updateSettings(input: UpdateSettingsInput!): UpdateSettingsResponse
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ const AssetSchema = new Schema(
|
||||
publication_date: Date,
|
||||
modified_date: Date,
|
||||
|
||||
// This object is used exclusivly for storing settings that are to override
|
||||
// This object is used exclusively for storing settings that are to override
|
||||
// the base settings from the base Settings object. This is to be accessed
|
||||
// always after running `rectifySettings` against it.
|
||||
settings: {
|
||||
|
||||
Reference in New Issue
Block a user