Merge branch 'master' into user-json

This commit is contained in:
Wyatt Johnson
2018-01-18 14:16:49 -07:00
9 changed files with 203 additions and 34 deletions
+73
View File
@@ -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);
+16 -15
View File
@@ -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 });
},
});
},
}),
}
);
+25 -12
View File
@@ -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();
+20
View File
@@ -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);
}
}
+3
View File
@@ -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);
},
+11
View File
@@ -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
View File
@@ -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: {