mirror of
https://github.com/wassname/talk.git
synced 2026-09-12 13:01:11 +08:00
Merge branch 'master' into organization-contact
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const morgan = require('morgan');
|
const trace = require('./middleware/trace');
|
||||||
|
const logging = require('./middleware/logging');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const merge = require('lodash/merge');
|
const merge = require('lodash/merge');
|
||||||
const helmet = require('helmet');
|
const helmet = require('helmet');
|
||||||
@@ -12,6 +13,10 @@ const { ENABLE_TRACING, APOLLO_ENGINE_KEY, PORT } = require('./config');
|
|||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
|
// Add the trace middleware first, it will create a request ID for each request
|
||||||
|
// downstream.
|
||||||
|
app.use(trace);
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
// PLUGIN PRE APPLICATION MIDDLEWARE
|
// PLUGIN PRE APPLICATION MIDDLEWARE
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
@@ -30,7 +35,7 @@ plugins.get('server', 'app').forEach(({ plugin, app: callback }) => {
|
|||||||
|
|
||||||
// Add the logging middleware only if we aren't testing.
|
// Add the logging middleware only if we aren't testing.
|
||||||
if (process.env.NODE_ENV !== 'test') {
|
if (process.env.NODE_ENV !== 'test') {
|
||||||
app.use(morgan('dev'));
|
app.use(logging.log);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ENABLE_TRACING && APOLLO_ENGINE_KEY) {
|
if (ENABLE_TRACING && APOLLO_ENGINE_KEY) {
|
||||||
|
|||||||
+7
-5
@@ -14,7 +14,7 @@ const SettingsService = require('../services/settings');
|
|||||||
const SetupService = require('../services/setup');
|
const SetupService = require('../services/setup');
|
||||||
const UsersService = require('../services/users');
|
const UsersService = require('../services/users');
|
||||||
const MigrationService = require('../services/migration');
|
const MigrationService = require('../services/migration');
|
||||||
const errors = require('../errors');
|
const { ErrSettingsInit, ErrSettingsNotInit } = require('../errors');
|
||||||
const Context = require('../graph/context');
|
const Context = require('../graph/context');
|
||||||
|
|
||||||
// Register the shutdown criteria.
|
// Register the shutdown criteria.
|
||||||
@@ -41,13 +41,15 @@ const performSetup = async () => {
|
|||||||
|
|
||||||
// We should NOT have gotten a settings object, this means that the
|
// We should NOT have gotten a settings object, this means that the
|
||||||
// application is already setup. Error out here.
|
// application is already setup. Error out here.
|
||||||
throw errors.ErrSettingsInit;
|
throw new ErrSettingsInit();
|
||||||
} catch (e) {
|
} catch (err) {
|
||||||
// If the error is `not init`, then we're good, otherwise, it's something
|
// If the error is `not init`, then we're good, otherwise, it's something
|
||||||
// else.
|
// else.
|
||||||
if (e !== errors.ErrSettingsNotInit) {
|
if (err instanceof ErrSettingsNotInit) {
|
||||||
throw e;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (program.defaults) {
|
if (program.defaults) {
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { render } from 'react-dom';
|
|
||||||
import { GraphQLDocs } from 'graphql-docs';
|
|
||||||
|
|
||||||
import fetcher from './services/fetcher';
|
|
||||||
|
|
||||||
// Render the application into the DOM
|
|
||||||
render(<GraphQLDocs fetcher={fetcher} />, document.querySelector('#root'));
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
export default function fetcher(query) {
|
|
||||||
return fetch(`${window.location.origin}/api/v1/graph/ql`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ query }),
|
|
||||||
}).then(res => res.json());
|
|
||||||
}
|
|
||||||
@@ -27,11 +27,14 @@ const TextField = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
TextField.propTypes = {
|
TextField.propTypes = {
|
||||||
|
id: PropTypes.string,
|
||||||
label: PropTypes.string,
|
label: PropTypes.string,
|
||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
errorMsg: PropTypes.string,
|
errorMsg: PropTypes.string,
|
||||||
type: PropTypes.string,
|
type: PropTypes.string,
|
||||||
|
className: PropTypes.string,
|
||||||
|
showErrors: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TextField;
|
export default TextField;
|
||||||
|
|||||||
@@ -122,6 +122,8 @@ sidebar:
|
|||||||
url: /commenter-features/
|
url: /commenter-features/
|
||||||
- title: Moderator Features
|
- title: Moderator Features
|
||||||
url: /moderator-features/
|
url: /moderator-features/
|
||||||
|
- title: User Roles in Talk
|
||||||
|
url: /roles/
|
||||||
- title: Trust
|
- title: Trust
|
||||||
url: /trust/
|
url: /trust/
|
||||||
- title: Toxic Comments
|
- title: Toxic Comments
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
title: User Roles in Talk
|
||||||
|
permalink: /roles/
|
||||||
|
---
|
||||||
|
|
||||||
|
We have four preset roles in Talk:
|
||||||
|
|
||||||
|
**Commenter**
|
||||||
|
* A standard community member
|
||||||
|
* Could receive a badge (eg. 'Subscriber') via [a custom newsroom Plugin Recipe](https://docs.coralproject.net/talk/plugin-recipes/#recipe-subscriber)
|
||||||
|
* No moderation abilities
|
||||||
|
* No configuration abilities
|
||||||
|
|
||||||
|
**Staff**
|
||||||
|
* A standard community member
|
||||||
|
* Receives a Staff badge when they comment
|
||||||
|
* Comments are automatically approved
|
||||||
|
* No moderation abilities
|
||||||
|
* No configuration abilities
|
||||||
|
|
||||||
|
**Moderator**
|
||||||
|
* A standard community member
|
||||||
|
* Receives a Staff badge when they comment
|
||||||
|
* Comments are automatically approved
|
||||||
|
* Has full moderation privileges
|
||||||
|
* Can configure individual articles via the Configure tab on the article page
|
||||||
|
* No site-wide configuration abilities
|
||||||
|
|
||||||
|
**Administrator**
|
||||||
|
* A standard community member
|
||||||
|
* Receives a Staff badge when they comment
|
||||||
|
* Comments are automatically approved
|
||||||
|
* Has full moderation privileges
|
||||||
|
* Can configure individual articles via the Configure tab on the article page
|
||||||
|
* Can configure site settings via the Configure tab in the moderation interface
|
||||||
@@ -10,21 +10,21 @@ class ExtendableError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* APIError is the base error that all application issued errors originate, they
|
* TalkError is the base error that all application issued errors originate,
|
||||||
* are composed of data used by the front end and backend to handle errors
|
* they are composed of data used by the front end and backend to handle errors
|
||||||
* consistently.
|
* consistently.
|
||||||
*/
|
*/
|
||||||
class APIError extends ExtendableError {
|
class TalkError extends ExtendableError {
|
||||||
constructor(
|
constructor(
|
||||||
message,
|
message,
|
||||||
{ status = 500, translation_key = null },
|
{ status = 500, translation_key = null } = {},
|
||||||
metadata = {}
|
metadata = {}
|
||||||
) {
|
) {
|
||||||
super(message);
|
super(message);
|
||||||
|
|
||||||
this.status = status;
|
this.status = status || 500;
|
||||||
this.translation_key = translation_key;
|
this.translation_key = translation_key || null;
|
||||||
this.metadata = metadata;
|
this.metadata = metadata || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
@@ -38,85 +38,114 @@ class APIError extends ExtendableError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ErrPasswordTooShort is returned when the password length is too short.
|
// ErrPasswordTooShort is returned when the password length is too short.
|
||||||
const ErrPasswordTooShort = new APIError(
|
class ErrPasswordTooShort extends TalkError {
|
||||||
'password must be at least 8 characters',
|
constructor() {
|
||||||
{
|
super('password must be at least 8 characters', {
|
||||||
status: 400,
|
status: 400,
|
||||||
translation_key: 'PASSWORD_LENGTH',
|
translation_key: 'PASSWORD_LENGTH',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
const ErrMissingEmail = new APIError('email is required', {
|
class ErrMissingEmail extends TalkError {
|
||||||
translation_key: 'EMAIL_REQUIRED',
|
constructor() {
|
||||||
status: 400,
|
super('email is required', {
|
||||||
});
|
translation_key: 'EMAIL_REQUIRED',
|
||||||
|
status: 400,
|
||||||
const ErrMissingPassword = new APIError('password is required', {
|
});
|
||||||
translation_key: 'PASSWORD_REQUIRED',
|
|
||||||
status: 400,
|
|
||||||
});
|
|
||||||
|
|
||||||
const ErrEmailTaken = new APIError('Email address already in use', {
|
|
||||||
translation_key: 'EMAIL_IN_USE',
|
|
||||||
status: 400,
|
|
||||||
});
|
|
||||||
|
|
||||||
const ErrUsernameTaken = new APIError('Username already in use', {
|
|
||||||
translation_key: 'USERNAME_IN_USE',
|
|
||||||
status: 400,
|
|
||||||
});
|
|
||||||
|
|
||||||
const ErrSameUsernameProvided = new APIError(
|
|
||||||
'Username provided for change is the same as current',
|
|
||||||
{
|
|
||||||
translation_key: 'SAME_USERNAME_PROVIDED',
|
|
||||||
status: 400,
|
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
const ErrSpecialChars = new APIError(
|
class ErrMissingPassword extends TalkError {
|
||||||
'No special characters are allowed in a username',
|
constructor() {
|
||||||
{
|
super('password is required', {
|
||||||
translation_key: 'NO_SPECIAL_CHARACTERS',
|
translation_key: 'PASSWORD_REQUIRED',
|
||||||
status: 400,
|
status: 400,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
const ErrMissingUsername = new APIError(
|
class ErrEmailTaken extends TalkError {
|
||||||
'A username is required to create a user',
|
constructor() {
|
||||||
{
|
super('Email address already in use', {
|
||||||
translation_key: 'USERNAME_REQUIRED',
|
translation_key: 'EMAIL_IN_USE',
|
||||||
status: 400,
|
status: 400,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
|
class ErrUsernameTaken extends TalkError {
|
||||||
|
constructor() {
|
||||||
|
super('Username already in use', {
|
||||||
|
translation_key: 'USERNAME_IN_USE',
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ErrSameUsernameProvided extends TalkError {
|
||||||
|
constructor() {
|
||||||
|
super('Username provided for change is the same as current', {
|
||||||
|
translation_key: 'SAME_USERNAME_PROVIDED',
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ErrSpecialChars extends TalkError {
|
||||||
|
constructor() {
|
||||||
|
super('No special characters are allowed in a username', {
|
||||||
|
translation_key: 'NO_SPECIAL_CHARACTERS',
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ErrMissingUsername extends TalkError {
|
||||||
|
constructor() {
|
||||||
|
super('A username is required to create a user', {
|
||||||
|
translation_key: 'USERNAME_REQUIRED',
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ErrEmailVerificationToken is returned in the event that the password reset is requested
|
// ErrEmailVerificationToken is returned in the event that the password reset is requested
|
||||||
// without a token.
|
// without a token.
|
||||||
const ErrEmailVerificationToken = new APIError('token is required', {
|
class ErrEmailVerificationToken extends TalkError {
|
||||||
translation_key: 'EMAIL_VERIFICATION_TOKEN_INVALID',
|
constructor() {
|
||||||
status: 400,
|
super('token is required', {
|
||||||
});
|
translation_key: 'EMAIL_VERIFICATION_TOKEN_INVALID',
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ErrEmailAlreadyVerified is returned when the user tries to verify an email
|
// ErrEmailAlreadyVerified is returned when the user tries to verify an email
|
||||||
// address that has already been verified.
|
// address that has already been verified.
|
||||||
const ErrEmailAlreadyVerified = new APIError(
|
class ErrEmailAlreadyVerified extends TalkError {
|
||||||
'email address is already verified',
|
constructor() {
|
||||||
{
|
super('email address is already verified', {
|
||||||
translation_key: 'EMAIL_ALREADY_VERIFIED',
|
translation_key: 'EMAIL_ALREADY_VERIFIED',
|
||||||
status: 409,
|
status: 409,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
// ErrPasswordResetToken is returned in the event that the password reset is requested
|
// ErrPasswordResetToken is returned in the event that the password reset is requested
|
||||||
// without a token.
|
// without a token.
|
||||||
const ErrPasswordResetToken = new APIError('token is required', {
|
class ErrPasswordResetToken extends TalkError {
|
||||||
translation_key: 'PASSWORD_RESET_TOKEN_INVALID',
|
constructor() {
|
||||||
status: 400,
|
super('token is required', {
|
||||||
});
|
translation_key: 'PASSWORD_RESET_TOKEN_INVALID',
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ErrAssetCommentingClosed is returned when a comment or action is attempted on
|
// ErrAssetCommentingClosed is returned when a comment or action is attempted on
|
||||||
// a stream where commenting has been closed.
|
// a stream where commenting has been closed.
|
||||||
class ErrAssetCommentingClosed extends APIError {
|
class ErrAssetCommentingClosed extends TalkError {
|
||||||
constructor(closedMessage = null) {
|
constructor(closedMessage = null) {
|
||||||
super(
|
super(
|
||||||
'asset commenting is closed',
|
'asset commenting is closed',
|
||||||
@@ -136,7 +165,7 @@ class ErrAssetCommentingClosed extends APIError {
|
|||||||
* ErrAuthentication is returned when there is an error authenticating and the
|
* ErrAuthentication is returned when there is an error authenticating and the
|
||||||
* message is provided.
|
* message is provided.
|
||||||
*/
|
*/
|
||||||
class ErrAuthentication extends APIError {
|
class ErrAuthentication extends TalkError {
|
||||||
constructor(message = null) {
|
constructor(message = null) {
|
||||||
super(
|
super(
|
||||||
'authentication error occurred',
|
'authentication error occurred',
|
||||||
@@ -154,7 +183,7 @@ class ErrAuthentication extends APIError {
|
|||||||
/**
|
/**
|
||||||
* ErrAlreadyExists is returned when an attempt to create a resource failed due to an existing one.
|
* ErrAlreadyExists is returned when an attempt to create a resource failed due to an existing one.
|
||||||
*/
|
*/
|
||||||
class ErrAlreadyExists extends APIError {
|
class ErrAlreadyExists extends TalkError {
|
||||||
constructor(existing = null) {
|
constructor(existing = null) {
|
||||||
super(
|
super(
|
||||||
'resource already exists',
|
'resource already exists',
|
||||||
@@ -171,121 +200,179 @@ class ErrAlreadyExists extends APIError {
|
|||||||
|
|
||||||
// ErrContainsProfanity is returned in the event that the middleware detects
|
// ErrContainsProfanity is returned in the event that the middleware detects
|
||||||
// profanity/banned/suspect words in the payload.
|
// profanity/banned/suspect words in the payload.
|
||||||
const ErrContainsProfanity = new APIError(
|
class ErrContainsProfanity extends TalkError {
|
||||||
'This username contains elements which are not permitted in our community. If you think this is in error, please contact us or try again.',
|
constructor(phrase) {
|
||||||
{
|
super(
|
||||||
translation_key: 'PROFANITY_ERROR',
|
'This username contains elements which are not permitted in our community. If you think this is in error, please contact us or try again.',
|
||||||
status: 400,
|
{
|
||||||
|
translation_key: 'PROFANITY_ERROR',
|
||||||
|
status: 400,
|
||||||
|
},
|
||||||
|
{ phrase }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
const ErrNotFound = new APIError('not found', {
|
class ErrNotFound extends TalkError {
|
||||||
translation_key: 'NOT_FOUND',
|
constructor() {
|
||||||
status: 404,
|
super('not found', {
|
||||||
});
|
translation_key: 'NOT_FOUND',
|
||||||
|
status: 404,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const ErrInvalidAssetURL = new APIError('asset_url is invalid', {
|
class ErrInvalidAssetURL extends TalkError {
|
||||||
translation_key: 'INVALID_ASSET_URL',
|
constructor() {
|
||||||
status: 400,
|
super('asset_url is invalid', {
|
||||||
});
|
translation_key: 'INVALID_ASSET_URL',
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ErrNotAuthorized is an error that is returned in the event an operation is
|
// ErrNotAuthorized is an error that is returned in the event an operation is
|
||||||
// deemed not authorized.
|
// deemed not authorized.
|
||||||
const ErrNotAuthorized = new APIError('not authorized', {
|
class ErrNotAuthorized extends TalkError {
|
||||||
translation_key: 'NOT_AUTHORIZED',
|
constructor() {
|
||||||
status: 401,
|
super('not authorized', {
|
||||||
});
|
translation_key: 'NOT_AUTHORIZED',
|
||||||
|
status: 401,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ErrSettingsNotInit is returned when the settings are required but not
|
// ErrSettingsNotInit is returned when the settings are required but not
|
||||||
// initialized.
|
// initialized.
|
||||||
const ErrSettingsNotInit = new Error(
|
class ErrSettingsNotInit extends TalkError {
|
||||||
'Talk is currently not setup. Please proceed to our web installer at $ROOT_URL/admin/install or run ./bin/cli-setup. Visit https://docs.coralproject.net/talk/ for more information on installation and configuration instructions'
|
constructor() {
|
||||||
);
|
super(
|
||||||
|
'Talk is currently not setup. Please proceed to our web installer at $ROOT_URL/admin/install or run ./bin/cli-setup. Visit https://docs.coralproject.net/talk/ for more information on installation and configuration instructions'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ErrSettingsInit is returned when the setup endpoint is hit and we are already
|
// ErrSettingsInit is returned when the setup endpoint is hit and we are already
|
||||||
// initialized.
|
// initialized.
|
||||||
const ErrSettingsInit = new APIError('settings are already initialized', {
|
class ErrSettingsInit extends TalkError {
|
||||||
status: 500,
|
constructor() {
|
||||||
});
|
super('settings are already initialized', {
|
||||||
|
status: 500,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ErrInstallLock is returned when the setup endpoint is hit and the install
|
// ErrInstallLock is returned when the setup endpoint is hit and the install
|
||||||
// lock is present.
|
// lock is present.
|
||||||
const ErrInstallLock = new APIError('install lock active', {
|
class ErrInstallLock extends TalkError {
|
||||||
status: 500,
|
constructor() {
|
||||||
});
|
super('install lock active', {
|
||||||
|
status: 500,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ErrPermissionUpdateUsername is returned when the user does not have permission to update their username.
|
// ErrPermissionUpdateUsername is returned when the user does not have permission to update their username.
|
||||||
const ErrPermissionUpdateUsername = new APIError(
|
class ErrPermissionUpdateUsername extends TalkError {
|
||||||
'You do not have permission to update your username.',
|
constructor() {
|
||||||
{
|
super('You do not have permission to update your username.', {
|
||||||
translation_key: 'EDIT_USERNAME_NOT_AUTHORIZED',
|
translation_key: 'EDIT_USERNAME_NOT_AUTHORIZED',
|
||||||
status: 403,
|
status: 403,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
// ErrLoginAttemptMaximumExceeded is returned when the login maximum is exceeded.
|
// ErrLoginAttemptMaximumExceeded is returned when the login maximum is exceeded.
|
||||||
const ErrLoginAttemptMaximumExceeded = new APIError(
|
class ErrLoginAttemptMaximumExceeded extends TalkError {
|
||||||
'You have made too many incorrect password attempts.',
|
constructor() {
|
||||||
{
|
super('You have made too many incorrect password attempts.', {
|
||||||
translation_key: 'LOGIN_MAXIMUM_EXCEEDED',
|
translation_key: 'LOGIN_MAXIMUM_EXCEEDED',
|
||||||
status: 429,
|
status: 429,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
// ErrEditWindowHasEnded is returned when the edit window has expired.
|
// ErrEditWindowHasEnded is returned when the edit window has expired.
|
||||||
const ErrEditWindowHasEnded = new APIError('Edit window is over', {
|
class ErrEditWindowHasEnded extends TalkError {
|
||||||
translation_key: 'EDIT_WINDOW_ENDED',
|
constructor() {
|
||||||
status: 403,
|
super('Edit window is over', {
|
||||||
});
|
translation_key: 'EDIT_WINDOW_ENDED',
|
||||||
|
status: 403,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ErrCommentTooShort is returned when the comment is too short.
|
// ErrCommentTooShort is returned when the comment is too short.
|
||||||
const ErrCommentTooShort = new APIError('Comment was too short', {
|
class ErrCommentTooShort extends TalkError {
|
||||||
translation_key: 'COMMENT_TOO_SHORT',
|
constructor(length) {
|
||||||
status: 400,
|
super(
|
||||||
});
|
'Comment was too short',
|
||||||
|
{
|
||||||
|
translation_key: 'COMMENT_TOO_SHORT',
|
||||||
|
status: 400,
|
||||||
|
},
|
||||||
|
{ length }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ErrAssetURLAlreadyExists is returned when a rename operation is requested
|
// ErrAssetURLAlreadyExists is returned when a rename operation is requested
|
||||||
// but an asset already exists with the new url.
|
// but an asset already exists with the new url.
|
||||||
const ErrAssetURLAlreadyExists = new APIError(
|
class ErrAssetURLAlreadyExists extends TalkError {
|
||||||
'Asset URL already exists, cannot rename',
|
constructor() {
|
||||||
{
|
super('Asset URL already exists, cannot rename', {
|
||||||
translation_key: 'ASSET_URL_ALREADY_EXISTS',
|
translation_key: 'ASSET_URL_ALREADY_EXISTS',
|
||||||
status: 409,
|
status: 409,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
// ErrNotVerified is returned when a user tries to login with valid credentials
|
// ErrNotVerified is returned when a user tries to login with valid credentials
|
||||||
// but their email address is not yet verified.
|
// but their email address is not yet verified.
|
||||||
const ErrNotVerified = new APIError(
|
class ErrNotVerified extends TalkError {
|
||||||
'User does not have a verified email address',
|
constructor() {
|
||||||
{
|
super('User does not have a verified email address', {
|
||||||
translation_key: 'EMAIL_NOT_VERIFIED',
|
translation_key: 'EMAIL_NOT_VERIFIED',
|
||||||
status: 401,
|
status: 401,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
const ErrMaxRateLimit = new APIError('Rate limit exceeded', {
|
class ErrMaxRateLimit extends TalkError {
|
||||||
translation_key: 'RATE_LIMIT_EXCEEDED',
|
constructor(max, tries) {
|
||||||
status: 429,
|
super(
|
||||||
});
|
'Rate limit exceeded',
|
||||||
|
{
|
||||||
|
translation_key: 'RATE_LIMIT_EXCEEDED',
|
||||||
|
status: 429,
|
||||||
|
},
|
||||||
|
{ tries, max }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ErrCannotIgnoreStaff is returned when a user tries to ignore a staff member.
|
// ErrCannotIgnoreStaff is returned when a user tries to ignore a staff member.
|
||||||
const ErrCannotIgnoreStaff = new APIError('Cannot ignore staff members.', {
|
class ErrCannotIgnoreStaff extends TalkError {
|
||||||
translation_key: 'CANNOT_IGNORE_STAFF',
|
constructor() {
|
||||||
status: 400,
|
super('Cannot ignore staff members.', {
|
||||||
});
|
translation_key: 'CANNOT_IGNORE_STAFF',
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ErrParentDoesNotVisible is returned when the user tries to reply to a comment
|
// ErrParentDoesNotVisible is returned when the user tries to reply to a comment
|
||||||
// that isn't visible.
|
// that isn't visible.
|
||||||
const ErrParentDoesNotVisible = new APIError(
|
class ErrParentDoesNotVisible extends TalkError {
|
||||||
'Cannot reply to a comment that is not visible',
|
constructor() {
|
||||||
{
|
super('Cannot reply to a comment that is not visible', {
|
||||||
translation_key: 'COMMENT_PARENT_NOT_VISIBLE',
|
translation_key: 'COMMENT_PARENT_NOT_VISIBLE',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
APIError,
|
TalkError,
|
||||||
ErrAlreadyExists,
|
ErrAlreadyExists,
|
||||||
ErrAssetCommentingClosed,
|
ErrAssetCommentingClosed,
|
||||||
ErrAssetURLAlreadyExists,
|
ErrAssetURLAlreadyExists,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const { forEachField } = require('./utils');
|
const { forEachField } = require('./utils');
|
||||||
const { maskErrors } = require('graphql-errors');
|
const { maskErrors } = require('graphql-errors');
|
||||||
const errors = require('../errors');
|
const { TalkError } = require('../errors');
|
||||||
const { Error: { ValidationError } } = require('mongoose');
|
const { Error: { ValidationError } } = require('mongoose');
|
||||||
|
|
||||||
// If an APIError happens in a mutation, then respond with `{errors: Array}`
|
// If an APIError happens in a mutation, then respond with `{errors: Array}`
|
||||||
@@ -11,7 +11,7 @@ const decorateWithMutationErrorHandler = field => {
|
|||||||
try {
|
try {
|
||||||
return await fieldResolver(obj, args, ctx, info);
|
return await fieldResolver(obj, args, ctx, info);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof errors.APIError) {
|
if (err instanceof TalkError) {
|
||||||
return {
|
return {
|
||||||
errors: [err],
|
errors: [err],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ const findOrCreateAssetByURL = async (ctx, url) => {
|
|||||||
try {
|
try {
|
||||||
new URL(url);
|
new URL(url);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw ErrInvalidAssetURL;
|
throw new ErrInvalidAssetURL(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try the easy lookup first.
|
// Try the easy lookup first.
|
||||||
@@ -76,7 +76,7 @@ const findOrCreateAssetByURL = async (ctx, url) => {
|
|||||||
|
|
||||||
// If the domain wasn't whitelisted, then we shouldn't create this asset!
|
// If the domain wasn't whitelisted, then we shouldn't create this asset!
|
||||||
if (!whitelisted) {
|
if (!whitelisted) {
|
||||||
throw ErrInvalidAssetURL;
|
throw new ErrInvalidAssetURL(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct the update operator that we'll use to create the asset.
|
// Construct the update operator that we'll use to create the asset.
|
||||||
@@ -135,7 +135,7 @@ const findByUrl = async (
|
|||||||
try {
|
try {
|
||||||
new URL(asset_url);
|
new URL(asset_url);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw errors.ErrInvalidAssetURL;
|
throw new errors.ErrInvalidAssetURL(asset_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Assets.findByUrl(asset_url);
|
return Assets.findByUrl(asset_url);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const errors = require('../../errors');
|
const { ErrNotFound, ErrNotAuthorized } = require('../../errors');
|
||||||
const { CREATE_ACTION, DELETE_ACTION } = require('../../perms/constants');
|
const { CREATE_ACTION, DELETE_ACTION } = require('../../perms/constants');
|
||||||
const { IGNORE_FLAGS_AGAINST_STAFF } = require('../../config');
|
const { IGNORE_FLAGS_AGAINST_STAFF } = require('../../config');
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ const createAction = async (
|
|||||||
// Gets the item referenced by the action.
|
// Gets the item referenced by the action.
|
||||||
const item = await getActionItem(ctx, { item_id, item_type });
|
const item = await getActionItem(ctx, { item_id, item_type });
|
||||||
if (!item || item === null) {
|
if (!item || item === null) {
|
||||||
throw errors.ErrNotFound;
|
throw new ErrNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are ignoring flags against staff, ensure that the target isn't a
|
// If we are ignoring flags against staff, ensure that the target isn't a
|
||||||
@@ -59,7 +59,7 @@ const createAction = async (
|
|||||||
// The item is a user, and this is a flag. Check to see if they are staff,
|
// The item is a user, and this is a flag. Check to see if they are staff,
|
||||||
// if they are, don't permit the flag.
|
// if they are, don't permit the flag.
|
||||||
if (item.isStaff()) {
|
if (item.isStaff()) {
|
||||||
throw errors.ErrNotAuthorized;
|
throw new ErrNotAuthorized();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,8 +108,8 @@ const deleteAction = (ctx, { id }) => {
|
|||||||
module.exports = ctx => {
|
module.exports = ctx => {
|
||||||
let mutators = {
|
let mutators = {
|
||||||
Action: {
|
Action: {
|
||||||
create: () => Promise.reject(errors.ErrNotAuthorized),
|
create: () => Promise.reject(new ErrNotAuthorized()),
|
||||||
delete: () => Promise.reject(errors.ErrNotAuthorized),
|
delete: () => Promise.reject(new ErrNotAuthorized()),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const errors = require('../../errors');
|
const { ErrNotAuthorized } = require('../../errors');
|
||||||
const {
|
const {
|
||||||
UPDATE_ASSET_SETTINGS,
|
UPDATE_ASSET_SETTINGS,
|
||||||
UPDATE_ASSET_STATUS,
|
UPDATE_ASSET_STATUS,
|
||||||
@@ -71,10 +71,10 @@ const scrapeAsset = async (ctx, id) => {
|
|||||||
module.exports = ctx => {
|
module.exports = ctx => {
|
||||||
let mutators = {
|
let mutators = {
|
||||||
Asset: {
|
Asset: {
|
||||||
updateSettings: () => Promise.reject(errors.ErrNotAuthorized),
|
updateSettings: () => Promise.reject(new ErrNotAuthorized()),
|
||||||
updateStatus: () => Promise.reject(errors.ErrNotAuthorized),
|
updateStatus: () => Promise.reject(new ErrNotAuthorized()),
|
||||||
closeNow: () => Promise.reject(errors.ErrNotAuthorized),
|
closeNow: () => Promise.reject(new ErrNotAuthorized()),
|
||||||
scrape: () => Promise.reject(errors.ErrNotAuthorized),
|
scrape: () => Promise.reject(new ErrNotAuthorized()),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const errors = require('../../errors');
|
const { ErrNotAuthorized } = require('../../errors');
|
||||||
const ActionModel = require('../../models/action');
|
const ActionModel = require('../../models/action');
|
||||||
const ActionsService = require('../../services/actions');
|
const ActionsService = require('../../services/actions');
|
||||||
const TagsService = require('../../services/tags');
|
const TagsService = require('../../services/tags');
|
||||||
@@ -312,9 +312,9 @@ const editComment = async (
|
|||||||
module.exports = ctx => {
|
module.exports = ctx => {
|
||||||
let mutators = {
|
let mutators = {
|
||||||
Comment: {
|
Comment: {
|
||||||
create: () => Promise.reject(errors.ErrNotAuthorized),
|
create: () => Promise.reject(new ErrNotAuthorized()),
|
||||||
setStatus: () => Promise.reject(errors.ErrNotAuthorized),
|
setStatus: () => Promise.reject(new ErrNotAuthorized()),
|
||||||
edit: () => Promise.reject(errors.ErrNotAuthorized),
|
edit: () => Promise.reject(new ErrNotAuthorized()),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const errors = require('../../errors');
|
const { ErrNotAuthorized } = require('../../errors');
|
||||||
|
|
||||||
const { UPDATE_SETTINGS } = require('../../perms/constants');
|
const { UPDATE_SETTINGS } = require('../../perms/constants');
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ const update = async (ctx, settings) => SettingsService.update(settings);
|
|||||||
module.exports = ctx => {
|
module.exports = ctx => {
|
||||||
let mutators = {
|
let mutators = {
|
||||||
Settings: {
|
Settings: {
|
||||||
update: () => Promise.reject(errors.ErrNotAuthorized),
|
update: () => Promise.reject(new ErrNotAuthorized()),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const TagsService = require('../../services/tags');
|
const TagsService = require('../../services/tags');
|
||||||
const errors = require('../../errors');
|
const { ErrNotAuthorized } = require('../../errors');
|
||||||
const {
|
const {
|
||||||
ADD_COMMENT_TAG,
|
ADD_COMMENT_TAG,
|
||||||
REMOVE_COMMENT_TAG,
|
REMOVE_COMMENT_TAG,
|
||||||
@@ -31,8 +31,8 @@ const modify = async (
|
|||||||
module.exports = context => {
|
module.exports = context => {
|
||||||
let mutators = {
|
let mutators = {
|
||||||
Tag: {
|
Tag: {
|
||||||
add: () => Promise.reject(errors.ErrNotAuthorized),
|
add: () => Promise.reject(new ErrNotAuthorized()),
|
||||||
remove: () => Promise.reject(errors.ErrNotAuthorized),
|
remove: () => Promise.reject(new ErrNotAuthorized()),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const errors = require('../../errors');
|
const { ErrNotAuthorized } = require('../../errors');
|
||||||
const TokensService = require('../../services/tokens');
|
const TokensService = require('../../services/tokens');
|
||||||
const { CREATE_TOKEN, REVOKE_TOKEN } = require('../../perms/constants');
|
const { CREATE_TOKEN, REVOKE_TOKEN } = require('../../perms/constants');
|
||||||
|
|
||||||
@@ -21,8 +21,8 @@ const revokeToken = async ({ user }, { id }) => {
|
|||||||
module.exports = context => {
|
module.exports = context => {
|
||||||
let mutators = {
|
let mutators = {
|
||||||
Token: {
|
Token: {
|
||||||
create: () => Promise.reject(errors.ErrNotAuthorized),
|
create: () => Promise.reject(new ErrNotAuthorized()),
|
||||||
revoke: () => Promise.reject(errors.ErrNotAuthorized),
|
revoke: () => Promise.reject(new ErrNotAuthorized()),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+11
-11
@@ -1,4 +1,4 @@
|
|||||||
const errors = require('../../errors');
|
const { ErrNotFound, ErrNotAuthorized } = require('../../errors');
|
||||||
const UsersService = require('../../services/users');
|
const UsersService = require('../../services/users');
|
||||||
const migrationHelpers = require('../../services/migration/helpers');
|
const migrationHelpers = require('../../services/migration/helpers');
|
||||||
const {
|
const {
|
||||||
@@ -92,7 +92,7 @@ const delUser = async (ctx, id) => {
|
|||||||
// Find the user we're removing.
|
// Find the user we're removing.
|
||||||
const user = await User.findOne({ id });
|
const user = await User.findOne({ id });
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw errors.ErrNotFound;
|
throw new ErrNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the query transformer we'll use to help batch process the user
|
// Get the query transformer we'll use to help batch process the user
|
||||||
@@ -156,15 +156,15 @@ const delUser = async (ctx, id) => {
|
|||||||
module.exports = ctx => {
|
module.exports = ctx => {
|
||||||
let mutators = {
|
let mutators = {
|
||||||
User: {
|
User: {
|
||||||
changeUsername: () => Promise.reject(errors.ErrNotAuthorized),
|
changeUsername: () => Promise.reject(new ErrNotAuthorized()),
|
||||||
ignoreUser: () => Promise.reject(errors.ErrNotAuthorized),
|
ignoreUser: () => Promise.reject(new ErrNotAuthorized()),
|
||||||
setRole: () => Promise.reject(errors.ErrNotAuthorized),
|
setRole: () => Promise.reject(new ErrNotAuthorized()),
|
||||||
setUserBanStatus: () => Promise.reject(errors.ErrNotAuthorized),
|
setUserBanStatus: () => Promise.reject(new ErrNotAuthorized()),
|
||||||
setUserSuspensionStatus: () => Promise.reject(errors.ErrNotAuthorized),
|
setUserSuspensionStatus: () => Promise.reject(new ErrNotAuthorized()),
|
||||||
setUserUsernameStatus: () => Promise.reject(errors.ErrNotAuthorized),
|
setUserUsernameStatus: () => Promise.reject(new ErrNotAuthorized()),
|
||||||
setUsername: () => Promise.reject(errors.ErrNotAuthorized),
|
setUsername: () => Promise.reject(new ErrNotAuthorized()),
|
||||||
stopIgnoringUser: () => Promise.reject(errors.ErrNotAuthorized),
|
stopIgnoringUser: () => Promise.reject(new ErrNotAuthorized()),
|
||||||
del: () => Promise.reject(errors.ErrNotAuthorized),
|
del: () => Promise.reject(new ErrNotAuthorized()),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
const { property } = require('lodash');
|
const { property } = require('lodash');
|
||||||
const { SEARCH_ACTIONS } = require('../../perms/constants');
|
const {
|
||||||
const { decorateWithTags, decorateWithPermissionCheck } = require('./util');
|
SEARCH_ACTIONS,
|
||||||
|
SEARCH_COMMENT_STATUS_HISTORY,
|
||||||
|
VIEW_BODY_HISTORY,
|
||||||
|
} = require('../../perms/constants');
|
||||||
|
const {
|
||||||
|
decorateWithTags,
|
||||||
|
decorateWithPermissionCheck,
|
||||||
|
checkSelfField,
|
||||||
|
} = require('./util');
|
||||||
|
|
||||||
const Comment = {
|
const Comment = {
|
||||||
hasParent({ parent_id }) {
|
hasParent({ parent_id }) {
|
||||||
@@ -60,9 +68,19 @@ const Comment = {
|
|||||||
// Decorate the Comment type resolver with a tags field.
|
// Decorate the Comment type resolver with a tags field.
|
||||||
decorateWithTags(Comment);
|
decorateWithTags(Comment);
|
||||||
|
|
||||||
// Protect direct action access.
|
// Protect direct action and status history access.
|
||||||
decorateWithPermissionCheck(Comment, {
|
decorateWithPermissionCheck(Comment, {
|
||||||
actions: [SEARCH_ACTIONS],
|
actions: [SEARCH_ACTIONS],
|
||||||
|
status_history: [SEARCH_COMMENT_STATUS_HISTORY],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Protect privileged fields.
|
||||||
|
decorateWithPermissionCheck(
|
||||||
|
Comment,
|
||||||
|
{
|
||||||
|
body_history: [VIEW_BODY_HISTORY],
|
||||||
|
},
|
||||||
|
checkSelfField('author_id')
|
||||||
|
);
|
||||||
|
|
||||||
module.exports = Comment;
|
module.exports = Comment;
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ const User = {
|
|||||||
return Comments.getByQuery(query);
|
return Comments.getByQuery(query);
|
||||||
},
|
},
|
||||||
|
|
||||||
ignoredUsers({ ignoresUsers }, args, { user, loaders: { Users } }) {
|
ignoredUsers({ ignoresUsers }, args, { loaders: { Users } }) {
|
||||||
// Return nothing if there is nothing to query for.
|
// Return nothing if there is nothing to query for.
|
||||||
if (!user.ignoresUsers || user.ignoresUsers.length <= 0) {
|
if (!ignoresUsers || ignoresUsers.length <= 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -505,8 +505,9 @@ type Comment {
|
|||||||
# The actual comment data.
|
# The actual comment data.
|
||||||
body: String!
|
body: String!
|
||||||
|
|
||||||
# The body history of the comment.
|
# The body history of the comment. Requires the `ADMIN` or `MODERATOR` role or
|
||||||
body_history: [CommentBodyHistory!]!
|
# the author.
|
||||||
|
body_history: [CommentBodyHistory!]
|
||||||
|
|
||||||
# The tags on the comment
|
# The tags on the comment
|
||||||
tags: [TagLink!]
|
tags: [TagLink!]
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { pluginsPath } = require('./plugins');
|
const { pluginsPath } = require('./plugins');
|
||||||
|
|
||||||
const buildTargets = ['coral-admin', 'coral-docs'];
|
const buildTargets = ['coral-admin'];
|
||||||
|
|
||||||
const buildEmbeds = ['stream'];
|
const buildEmbeds = ['stream'];
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -4,7 +4,6 @@ const { createLogger } = require('../services/logging');
|
|||||||
const logger = createLogger('jobs:mailer');
|
const logger = createLogger('jobs:mailer');
|
||||||
const Context = require('../graph/context');
|
const Context = require('../graph/context');
|
||||||
const { get } = require('lodash');
|
const { get } = require('lodash');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
SMTP_HOST,
|
SMTP_HOST,
|
||||||
SMTP_USERNAME,
|
SMTP_USERNAME,
|
||||||
@@ -12,6 +11,7 @@ const {
|
|||||||
SMTP_PASSWORD,
|
SMTP_PASSWORD,
|
||||||
SMTP_FROM_ADDRESS,
|
SMTP_FROM_ADDRESS,
|
||||||
} = require('../config');
|
} = require('../config');
|
||||||
|
const { ErrMissingEmail } = require('../errors');
|
||||||
|
|
||||||
// parseSMTPPort will return the port for SMTP.
|
// parseSMTPPort will return the port for SMTP.
|
||||||
const parseSMTPPort = () => {
|
const parseSMTPPort = () => {
|
||||||
@@ -99,7 +99,7 @@ const getEmailAddress = async ({ email, user }) => {
|
|||||||
|
|
||||||
const email = get(data, 'user.email');
|
const email = get(data, 'user.email');
|
||||||
if (!email) {
|
if (!email) {
|
||||||
throw errors.ErrMissingEmail;
|
throw new ErrMissingEmail();
|
||||||
}
|
}
|
||||||
|
|
||||||
return email;
|
return email;
|
||||||
|
|||||||
@@ -0,0 +1,465 @@
|
|||||||
|
fi_FI:
|
||||||
|
your_account_has_been_suspended: Tilisi on väliaikasesti suljettu.
|
||||||
|
your_account_has_been_banned: Tilillesi on asetettu kirjoituskielto.
|
||||||
|
your_username_has_been_rejected: Tilisi on suljettu, koska käyttäjänimesi on epäsopiva. Vaihda käyttäjänimeä jatkaaksesi tilin käyttöä.
|
||||||
|
embed_comments_tab: Kommentit
|
||||||
|
bandialog:
|
||||||
|
are_you_sure: "Haluatko varmasti asettaa kirjoituskiellon käyttäjätilille {0}?"
|
||||||
|
ban_user: "Estä käyttäjä?"
|
||||||
|
banned_user: "Estetty käyttäjä"
|
||||||
|
cancel: "Peruuta"
|
||||||
|
note: "Huom! {0}"
|
||||||
|
note_reject_comment: "Käyttäjän asettaminen kirjoituskieltoon asettaa myös kommentin Hylätyt-jonoon"
|
||||||
|
note_ban_user: "Käyttäjän kirjoituskielto estää kommentoinnin, kommentteihin reagoinnin, sekä kommenttien ilmiantamisen."
|
||||||
|
yes_ban_user: "Kyllä, aseta käyttäjälle kirjoituskielto"
|
||||||
|
write_a_message: "Kirjoita viesti"
|
||||||
|
send: "Lähetä"
|
||||||
|
notify_ban_headline: "Lähetä käyttäjälle ilmoitus kirjoituskiellosta"
|
||||||
|
notify_ban_description: "Tämä lähettää käyttäjälle sähköposti-ilmoituksen kirjoituskiellosta."
|
||||||
|
email_message_ban: "{0},\n\nTilläsi on rikottu kommentoinnin sääntöjä, jonka takia tilille on asetettu kirjoituskielto. Tiliä ei voida enää käyttää kommentointiin osallistumiseen tai kommenttien ilmiantamiseen. Ota yhteyttä moderointiin, jos tämä on tapahtunut mielestäsi väärin perustein."
|
||||||
|
bio_offensive: "Kuvaus on loukkaava"
|
||||||
|
cancel: "Peruuta"
|
||||||
|
confirm_email:
|
||||||
|
click_to_confirm: "Vahvista sähköpostiosoitteesi"
|
||||||
|
confirm: "Vahvista"
|
||||||
|
password_reset:
|
||||||
|
mail_sent: "Salasananvaihtolinkki on lähetetty rekisteröityyn sähköpostiosoitteeseen"
|
||||||
|
set_new_password: "Luo uusi salasana"
|
||||||
|
new_password: "Uusi salasana"
|
||||||
|
new_password_help: "Salasanan tulee olla vähintään 8 merkin pituinen"
|
||||||
|
confirm_new_password: "Vahvista uusi salasana"
|
||||||
|
change_password: "Vaihda salasana"
|
||||||
|
characters_remaining: "merkki(ä) jäljellä"
|
||||||
|
comment:
|
||||||
|
anon: "Anonyymi"
|
||||||
|
undo_reject: "Peruuta"
|
||||||
|
ban_user: "Estä käyttäjä"
|
||||||
|
comment: "Kommentoi"
|
||||||
|
edited: Muokattu
|
||||||
|
flagged: "ilmiannettu"
|
||||||
|
view_context: "Näytä konteksti"
|
||||||
|
comment_box:
|
||||||
|
post: "Lähetä"
|
||||||
|
cancel: "Peruuta"
|
||||||
|
reply: "Vastaa"
|
||||||
|
comment: "Kommentoi"
|
||||||
|
name: "Nimi"
|
||||||
|
comment_post_notif: "Kommenttisi on lähetetty."
|
||||||
|
comment_post_notif_premod: "Kiitos kommentistasi. Moderointitiimimme käsittelee kommenttisi mahdollisimman pian."
|
||||||
|
comment_post_banned_word: "Kommenttisi sisältää vähintään yhden kielletyn sanan, joten kommenttiasi ei tulla julkaisemaan. Jos tämä ilmoitus on mielestäsi aiheeton, olethan yhteydessä moderointitiimiimme."
|
||||||
|
characters_remaining: "merkki(ä) jäljellä"
|
||||||
|
comment_offensive: "Kommentti on loukkaava"
|
||||||
|
comment_singular: Kommentti
|
||||||
|
comment_plural: Kommentit
|
||||||
|
comment_post_banned_word: "Kommenttisi sisältää vähintään yhden kielletyn sanan, joten kommenttiasi ei tulla julkaisemaan. Jos tämä ilmoitus on mielestäsi aiheeton, olethan yhteydessä moderointitiimiimme."
|
||||||
|
comment_post_notif: "Kommenttisi on lähetetty."
|
||||||
|
comment_post_notif_premod: "Kiitos kommentistasi. Moderointitiimimme käsittelee kommenttisi mahdollisimman pian."
|
||||||
|
common:
|
||||||
|
copy: 'Kopioi'
|
||||||
|
error: 'Tapahtui virhe.'
|
||||||
|
reply: 'vastaa'
|
||||||
|
replies: 'vastaukset'
|
||||||
|
reaction: 'reaktio'
|
||||||
|
reactions: 'reaktiot'
|
||||||
|
story: 'Artikkeli'
|
||||||
|
flagged_usernames:
|
||||||
|
notify_approved: '{0} hyväksyi käyttäjänimen {1}'
|
||||||
|
notify_rejected: '{0} hylkäsi käyttäjänimen {1}'
|
||||||
|
notify_flagged: '{0} ilmiantoi käyttäjänimen {1}'
|
||||||
|
notify_changed: 'käyttäjä {0} vaihtoi käyttäjänimesä muotoon {1}'
|
||||||
|
community:
|
||||||
|
account_creation_date: "Tilin luontiaika"
|
||||||
|
active: Aktiivinen
|
||||||
|
admin: Ylläpitäjä
|
||||||
|
ads_marketing: "Vaikuttaa mainokselta"
|
||||||
|
are_you_sure: "Haluatka varmasti asettaa käyttäjlle {0} kirjoituskiellon?"
|
||||||
|
ban_user: "Estä käyttäjä?"
|
||||||
|
banned: Estetty
|
||||||
|
banned_user: "Estetty käyttäjä"
|
||||||
|
cancel: Peruuta
|
||||||
|
dont_like_username: "Epäsopiva käyttäjänimi"
|
||||||
|
flaggedaccounts: "Ilmiannetut käyttäjänimet"
|
||||||
|
flags: Liputuksia
|
||||||
|
impersonating: "Toiseksi tekeytyminen"
|
||||||
|
loading: "Ladataan tuloksia"
|
||||||
|
moderator: Moderaattori
|
||||||
|
newsroom_role: "Uutishuoneen rooli"
|
||||||
|
no_flagged_accounts: "Ilmiannettujen nimimerkkien jono on tyhjä."
|
||||||
|
no_results: "Antamallasi hakusanalla ei löydy yhtään käyttäjää."
|
||||||
|
offensive: "Loukkaava"
|
||||||
|
other: Muu
|
||||||
|
people: Käyttäjät
|
||||||
|
role: "Valitse rooli..."
|
||||||
|
select_status: "Valitse tila..."
|
||||||
|
spam_ads: "Roskapostit/mainokset"
|
||||||
|
staff: "Työntekijä"
|
||||||
|
status: Tila
|
||||||
|
username_and_email: "Käyttäjänimi ja sähköposti"
|
||||||
|
yes_ban_user: "Kyllä, estä käyttäjä"
|
||||||
|
commenter: "Kommentoija"
|
||||||
|
configure:
|
||||||
|
apply: Käytä
|
||||||
|
banned_word_text: "Näitä sanoja tai fraaseja sisältävät kommentit poistetaan automaattisesti. Lisää uusi kirjoittamalla sana ja painamalla enter- tai tab-näppäintä. Vaihtoehtoisesti kopio lista, jossa sanat on eroteltu pilkuilla."
|
||||||
|
banned_words_title: "Estettyjen sanojen lista."
|
||||||
|
close: "Sulje"
|
||||||
|
close_after: "Sulje kommentit, kun on kulunut"
|
||||||
|
close_stream: "Sulje kommentointi"
|
||||||
|
close_stream_configuration: "Kommentointi suljettu. Kommentointi on mahdollista, jos avaat kommentoinnin uudelleen."
|
||||||
|
closed_comments_desc: "Kirjoita viesti, joka näytetään, kun kommenttivirta on suljettu ja uusia viestejä ei voi enää lähettää."
|
||||||
|
closed_comments_label: "Kirjoita viesti..."
|
||||||
|
closed_stream_settings: "Suljetun keskustelun ilmoitusviesti"
|
||||||
|
comment_count_error: "Syötä numero."
|
||||||
|
comment_count_header: "Rajoita kommentin pituutta"
|
||||||
|
comment_count_text_post: merkkiin
|
||||||
|
comment_count_text_pre: "Kommentin pituus rajoitetaan"
|
||||||
|
comment_settings: Asetukset
|
||||||
|
comment_stream: "Kommentit"
|
||||||
|
comment_stream_will_close: "Kommentointi sulkeutuu"
|
||||||
|
community: Yhteisö
|
||||||
|
configure: "Muokkaa asetuksia"
|
||||||
|
copy_and_paste: "Kopio ja liitä koodi sisällönhallintajärjestelmääsi upottaaksesi kommenttiosion artikkeliin."
|
||||||
|
custom_css_url: "CSS-tiedoston URL"
|
||||||
|
custom_css_url_desc: "CSS-tiedoston URL, jonka sisällöllä ylikirjoitetaan oletustyylit. Voi olla sisäinen tai ulkoinen."
|
||||||
|
days: Päivää
|
||||||
|
description: "Ylläpitäjänä voit muokata tämän artikkelin kommentoinnin asetuksia:"
|
||||||
|
domain_list_text: "Syötä verkkotunnukset, joilla on lupa käyttää Talkia. Esimerkiksi lokaalikehitys-, QA- ja tuotantoympäristöt: localhost:3000 staging.domain.com domain.com."
|
||||||
|
domain_list_title: "Luviteut verkkotunnukset"
|
||||||
|
edit_comment_timeframe_heading: "Muokkaa kommentin muokkausaikaikkunaa"
|
||||||
|
edit_comment_timeframe_text_pre: "Kommentoijilla on"
|
||||||
|
edit_comment_timeframe_text_post: "sekuntia aikaa muokata kommenttejaan."
|
||||||
|
embed_comment_stream: "Upota keskustelu"
|
||||||
|
enable_premod_links_text: Moderaattorien tulee hyväksyä sellaisten kommenttien julkaisu, joissa on linkki.
|
||||||
|
enable_pre_moderation: "Esimoderointi päälle"
|
||||||
|
enable_pre_moderation_text: "Moderaattorien tulee hyväksyä kaikki julkaistavat kommentit."
|
||||||
|
enable_premod_links: "Esimoderoi kommentit, joissa on linkki"
|
||||||
|
enable_premod: "Esimoderointi päälle"
|
||||||
|
enable_premod_description: "Moderaattorien tulee hyväksyä kaikki julkaistavat kommentit."
|
||||||
|
enable_premod_links_description: "Moderaattorien tulee hyväksyä sellaisten kommenttien julkaisu, joissa on linkki."
|
||||||
|
enable_questionbox: "Kysy lukijoilta"
|
||||||
|
enable_questionbox_description: "Tämä kysymys tulee näkymään kommenttiosion ylälaidassa. Kysy artikkelin aiheesta tai ohjaa keskustelua kysymyksen avulla."
|
||||||
|
hours: Tuntia
|
||||||
|
include_comment_stream: "Sisällytä kommentoinnin kuvaus lukijoille"
|
||||||
|
include_comment_stream_desc: "Kirjoita kommenttiosion yläreunassa näkyvä viesti. Aseta keskustelun aihe, sisällytä sääntöjä tms."
|
||||||
|
include_text: "Lisää teksti tähän."
|
||||||
|
include_question_here: "Kirjoita kysymyksesi tähän:"
|
||||||
|
moderate: Moderoi
|
||||||
|
moderation_settings: "Moderointiasetukset"
|
||||||
|
open: "Avaa"
|
||||||
|
open_stream: "Avaa kommentointi"
|
||||||
|
open_stream_configuration: "Tämän artikkelin kommentointi on tällä hetkellä auki. Jos se suljetaan, ei kommentointi ole enää mahdollista, mutta vanhat kommentit jäävät näkyviin."
|
||||||
|
require_email_verification: "Vaadi sähköpostin vahvistus"
|
||||||
|
require_email_verification_text: "Uusien käyttäjien täytyy vahvistaa sähköpostiosoitteensa ennen kommentoinnin aloittamista"
|
||||||
|
save_changes: "Tallenna muutokset"
|
||||||
|
shortcuts: Pikalinkit
|
||||||
|
sign_out: "Kirjaudu ulos"
|
||||||
|
stories: Artikkelitarinat
|
||||||
|
stream_settings: "Kommentoinnin asetukset"
|
||||||
|
suspect_word_title: "Epäilyttävien sanojen lista"
|
||||||
|
suspect_word_text: "Nämä sanat tai fraasit näkyvät korostettuina kommenteissa. Lisää uusi kirjoittamalla sana ja painamalla enter- tai tab-näppäintä. Vaihtoehtoisesti kopio lista, jossa sanat on eroteltu pilkuilla."
|
||||||
|
tech_settings: "Tekniset asetukset"
|
||||||
|
title: "Muokkaa kommentoinnin asetuksia"
|
||||||
|
weeks: Viikkoa
|
||||||
|
wordlist: "Kielletyt sanat"
|
||||||
|
continue: "Jatka"
|
||||||
|
createdisplay:
|
||||||
|
check_the_form: "Tarkista syöttämäsi tiedot"
|
||||||
|
continue: "Käytä Facebook-käyttäjänimeä"
|
||||||
|
error_create: "Käyttäjänimen vaihdossa tapahtui virhe"
|
||||||
|
fake_comment_body: "Tämä on esimerkkikommentti. Lukijat voivat jakaa mielipiteitään ja näkemyksiään kommenttiosiossa."
|
||||||
|
fake_comment_date: "1 minuutti sitten"
|
||||||
|
if_you_dont_change_your_name: "Facebook-käyttäjänimesi näkyy kommenttiesi yhteydessä, ellet tässä vaiheessa vaihda käyttäjänimeäsi."
|
||||||
|
required_field: "Vaadittu tieto"
|
||||||
|
save: Tallenna
|
||||||
|
special_characters: "Käyttäjänimissä sallittuja merkkejä ovat ainoastaan kirjaimet, numerot, sekä alaviiva"
|
||||||
|
username: Käyttäjänimi
|
||||||
|
write_your_username: "Muokkaa käyttäjänimeäsi"
|
||||||
|
your_username: "Käyttäjänimesi näkyy jokaisen kommenttisi yhteydessä"
|
||||||
|
done: Valmis
|
||||||
|
edit_comment:
|
||||||
|
body_input_label: "Muokkaa tätä kommenttia"
|
||||||
|
save_button: "Tallenna muutokset"
|
||||||
|
edit_window_expired: "Et voi enää muokata tätä kommenttia, koska muokkauksen aikaikkuna on umpeutunut. Jätä sen sijaan uusi kommentti?"
|
||||||
|
edit_window_expired_close: "Sulje"
|
||||||
|
edit_window_timer_prefix: "Muokkauksen aikaikkunaa jäljellä: "
|
||||||
|
second: "sekunti"
|
||||||
|
seconds_plural: "sekuntia"
|
||||||
|
minute: "minuutti"
|
||||||
|
minutes_plural: "minuuttia"
|
||||||
|
email:
|
||||||
|
suspended:
|
||||||
|
subject: "Tilisi on väliaikaisesti suljettu"
|
||||||
|
banned:
|
||||||
|
subject: "Tilisi on asetettu kirjoituskieltoon"
|
||||||
|
body: "Tilisi on asetettu kirjoituskieltoon. Et voi osallistua keskusteluun kirjoituskiellon aikana."
|
||||||
|
confirm:
|
||||||
|
has_been_requested: "Sähköpostivahvistus on pyydetty tilille:"
|
||||||
|
to_confirm: "Vahvista tili klikkaamalla seuraavaa linkkiä:"
|
||||||
|
confirm_email: "Vahvista sähköposti"
|
||||||
|
if_you_did_not: "Jätä tämä viesti huomioimatta, jos et ole tehnyt pyyntöä."
|
||||||
|
subject: "Sähköpostin vahvistus"
|
||||||
|
password_reset:
|
||||||
|
we_received_a_request: "Tilisi salasanan vaihtoa on pyydetty. Jätä tämä viesti huomioimatta, jos et ole tehnyt pyyntöä."
|
||||||
|
if_you_did: "Jos pyysit,"
|
||||||
|
please_click: "klikkaa tästä vaihtaaksesi salasanasi."
|
||||||
|
embedlink:
|
||||||
|
copy: "Kopioi leikepöydälle"
|
||||||
|
error:
|
||||||
|
COMMENT_PARENT_NOT_VISIBLE: "Kommenttia, johon yrität vastata, ei enää ole."
|
||||||
|
EMAIL_VERIFICATION_TOKEN_INVALID: "Sähköpostin vahvistusvarmiste on epävalidi."
|
||||||
|
PASSWORD_RESET_TOKEN_INVALID: "Salasananvaihtolinkki on epävalidi."
|
||||||
|
COMMENT_TOO_SHORT: "Kommentin tulee olla vähintään kaksi merkkiä pitkä. Tarkista kirjoittamasi teksti."
|
||||||
|
NOT_AUTHORIZED: "Sinulla ei ole oikeutta suorittaa tätä toimintoa."
|
||||||
|
NO_SPECIAL_CHARACTERS: "Käyttäjänimissä sallittuja merkkejä ovat ainoastaan kirjaimet, numerot, sekä alaviiva"
|
||||||
|
PASSWORD_LENGTH: "Salasana on liian lyhyt"
|
||||||
|
PROFANITY_ERROR: "Käyttäjänimet eivät saa sisältää hävyttömyyksiä. Ota yhteyttä ylläpitoon, jos mielestäsi on tapahtunut virhe."
|
||||||
|
RATE_LIMIT_EXCEEDED: "Raja-arvo on ylittynyt"
|
||||||
|
USERNAME_IN_USE: "Käyttäjänimi jo käytössä"
|
||||||
|
USERNAME_REQUIRED: "Syötä käyttäjänimi"
|
||||||
|
EMAIL_NOT_VERIFIED: "Sähköpostiosoitetta ei ole vahvistettu"
|
||||||
|
EDIT_WINDOW_ENDED: "Et voi enää muokata tätä kommenttia, koska muokkauksen aikaikkuna on umpeutunut."
|
||||||
|
EDIT_USERNAME_NOT_AUTHORIZED: "Sinulla ei ole oikeutta päivittää tai muokata käyttäjänimeä."
|
||||||
|
SAME_USERNAME_PROVIDED: "Anna eri käyttäjänimi."
|
||||||
|
EMAIL_IN_USE: "Sähköpostiosoite on jo käytössä"
|
||||||
|
EMAIL_REQUIRED: "Syötä sähköpostiosoite"
|
||||||
|
LOGIN_MAXIMUM_EXCEEDED: "Olet tehnyt liian monta epäonnistunutta yritystä. Odota, ole hyvä."
|
||||||
|
PASSWORD_REQUIRED: "Syötä salasana"
|
||||||
|
COMMENTING_CLOSED: "Kommentointi on suljettu"
|
||||||
|
NOT_FOUND: "Resurssia ei löydy"
|
||||||
|
ALREADY_EXISTS: "Resurssi on jo olemassa"
|
||||||
|
INVALID_ASSET_URL: "Tarkista tiedoston URL"
|
||||||
|
CANNOT_IGNORE_STAFF: "Työntekijöitä ei voi jättää huomioimatta"
|
||||||
|
email: "Tarkista sähköpostiosoite"
|
||||||
|
confirm_password: "Salasanat eivät täsmää. Tarkista, ole hyvä."
|
||||||
|
network_error: "Palvelimeen yhdistäminen epäonnistui. Tarkista internetyhteytesi."
|
||||||
|
email_not_verified: "Sähköpostiosoitetta {0} ei ole vahvistettu."
|
||||||
|
email_password: "Sähköpostiosoite ja/tai salasana on väärä."
|
||||||
|
organization_name: "Organisaation nimessä voi käyttää vain kirjaimia ja numeroita."
|
||||||
|
password: "Salasanan tulee olla vähintään 8 merkkiä pitkä"
|
||||||
|
username: "Käyttäjänimissä sallittuja merkkejä ovat ainoastaan kirjaimet, numerot, sekä alaviiva"
|
||||||
|
unexpected: "Tapahtui odottamaton virhe. Pahiottelemme!"
|
||||||
|
required_field: "Tämä on vaadittu kenttä"
|
||||||
|
temporarily_suspended: "Tilisi on suljettu väliaikaisesti. Se aktivoituu uudelleen {0}. Ota yhteyttä, jos on sinulla on aiheesta kysyttävää."
|
||||||
|
flag_comment: "Ilmianna kommentti"
|
||||||
|
flag_reason: "Ilmiannon syy (ei pakollinen)"
|
||||||
|
flag_username: "Ilmianna käyttäjä"
|
||||||
|
framework:
|
||||||
|
banned_account_header: "Tilisi on kirjoituskiellossa"
|
||||||
|
banned_account_body: "Et pysty kirjoittamaan tai ilmiantamaan kommentteja."
|
||||||
|
comment: kommentti
|
||||||
|
comment_is_ignored: "Tämä kommentti on piilossa, koska olet päättänyt jättää kommentin kirjoittajan huomiotta."
|
||||||
|
comment_is_rejected: "Olet piilottanut tämän kommentin."
|
||||||
|
comment_is_hidden: "Tämä kommentti ei ole saatavilla."
|
||||||
|
comments: kommentit
|
||||||
|
configure_stream: "Muokkaa asetuksia"
|
||||||
|
content_not_available: "Sisältö ei ole saatavilla"
|
||||||
|
edit_name:
|
||||||
|
button: Lähetä
|
||||||
|
error: "Käyttäjänimissä sallittuja merkkejä ovat ainoastaan kirjaimet, numerot, sekä alaviiva"
|
||||||
|
label: "Uusi käyttäjänimi"
|
||||||
|
msg: "Tilisi on suljettu väliaikaisesti, koska käyttäjänimi on todettu sopimattomaksi. Vaihda käyttäjänimi, jos haluat jatkaa tilin käyttöä. Ole meihin yhteydessä, jos sinulla on aiheesta kysyttävää."
|
||||||
|
changed_name:
|
||||||
|
msg: "Käyttäjänimen vaihto on moderointitiimillämme tarkistuksessa."
|
||||||
|
my_comments: "Kommenttini"
|
||||||
|
my_profile: "Profiilini"
|
||||||
|
new_count: "Näytä {0} lisää {1}"
|
||||||
|
profile: Profiili
|
||||||
|
show_all_comments: "Näytä kaikki kommentit"
|
||||||
|
success_bio_update: "Kuvauksesi on päivitetty"
|
||||||
|
success_name_update: "Käyttäjänimesi on päivitetty"
|
||||||
|
success_update_settings: "Tekemäsi muutokset on otettu käyttöön"
|
||||||
|
show_all_replies: Näytä kaikki vastaukset
|
||||||
|
show_more_replies: Näytä lisää vastauksia
|
||||||
|
view_more_comments: "näytä lisää kommentteja"
|
||||||
|
view_reply: "näytä vastaus"
|
||||||
|
from_settings_page: "Näet kommentointihistoriasi profiilisivulta."
|
||||||
|
like: Tykkää
|
||||||
|
loading_results: "Ladataan tuloksia"
|
||||||
|
marketing: "Vaikuttaa mainokselta"
|
||||||
|
moderate_this_stream: "Moderoi tätä kommentointia"
|
||||||
|
flags:
|
||||||
|
reasons:
|
||||||
|
user:
|
||||||
|
username_offensive: "Loukkaava"
|
||||||
|
username_nolike: "En tykkää"
|
||||||
|
username_impersonating: "Toisena esiintyminen"
|
||||||
|
username_spam: "Roskaviesti"
|
||||||
|
username_other: "Muu"
|
||||||
|
comment:
|
||||||
|
comment_offensive: "Loukkaava"
|
||||||
|
comment_spam: "Roskaviesti"
|
||||||
|
comment_noagree: "Olen eri mieltä"
|
||||||
|
comment_other: "Muu"
|
||||||
|
suspect_word: "Epäilyttävä sana"
|
||||||
|
banned_word: "Kielletty sana"
|
||||||
|
body_count: "Liian pitkä viesti"
|
||||||
|
trust: "Luotettava"
|
||||||
|
links: "Linkki"
|
||||||
|
modqueue:
|
||||||
|
account: "Liputuksia"
|
||||||
|
actions: Toiminnot
|
||||||
|
all: kaikki
|
||||||
|
all_streams: "Kaikki keskustelut"
|
||||||
|
notify_edited: '{0} muokkasi kommenttia "{1}"'
|
||||||
|
notify_accepted: '{0} hyväksyi kommentin "{1}"'
|
||||||
|
notify_rejected: '{0} hylkäsi kommentin "{1}"'
|
||||||
|
notify_flagged: '{0} ilmiantoi kommentin "{1}"'
|
||||||
|
notify_reset: '{0} tyhjensi kommentin "{1}" tilan'
|
||||||
|
approve: "Hyväksy"
|
||||||
|
approved: "Hyväksytty"
|
||||||
|
ban_user: "Kirjoituskielto käyttäjälle"
|
||||||
|
billion: mrd
|
||||||
|
close: Sulje
|
||||||
|
empty_queue: "Moderointijono on tyhjä."
|
||||||
|
flagged: liputettu
|
||||||
|
reported: ilmiannettu
|
||||||
|
less_detail: "Vähemmän yksityiskohtia"
|
||||||
|
likes: tykkäyksiä
|
||||||
|
million: milj.
|
||||||
|
mod_faster: "Moderoi nopeammin käyttäen pikanäppäimiä"
|
||||||
|
moderate: "Moderoi →"
|
||||||
|
more_detail: "Enemmän yksityiskohtia"
|
||||||
|
new: Uusi
|
||||||
|
newest_first: "Uusin ensin"
|
||||||
|
navigation: Navioginti
|
||||||
|
next_comment: "Seuraava kommentti"
|
||||||
|
toggle_search: "Avaa haku"
|
||||||
|
next_queue: "Vaihda jonoa"
|
||||||
|
oldest_first: "Vanhin ensin"
|
||||||
|
premod: esimoderoi
|
||||||
|
prev_comment: "Edellinen kommentti"
|
||||||
|
reject: "Hylkää"
|
||||||
|
rejected: "Hylätty"
|
||||||
|
reply: "Vastaa"
|
||||||
|
select_stream: "Valitse kommenttivirta"
|
||||||
|
shift_key: "⇧"
|
||||||
|
shortcuts: "Pikalinkit"
|
||||||
|
sort: "Järjestä"
|
||||||
|
show_shortcuts: "Näytä pikalinkit"
|
||||||
|
singleview: "Zen-moodi"
|
||||||
|
thismenu: "Avaa valikko"
|
||||||
|
jump_to_queue: "Siirry tiettyyn jonoon"
|
||||||
|
thousand: tuhatta
|
||||||
|
try_these: "Kokeile näitä"
|
||||||
|
view_more_shortcuts: "Näytä enemmän pikalinkkejä"
|
||||||
|
my_comment_history: "Kommentointihistoriani"
|
||||||
|
name: Nimi
|
||||||
|
no_agree_comment: "En ole samaa mieltä"
|
||||||
|
no_like_bio: "En pidä kuvauksesta"
|
||||||
|
no_like_username: "En pidä käyttäjänimestä"
|
||||||
|
already_flagged_username: "Olet jo ilmiantanut tämän käyttäjänimen."
|
||||||
|
other: Muu
|
||||||
|
permalink: Jaa
|
||||||
|
personal_info: "Kommentti sisältää henkilökohtaisesti tunnistettavia tietoja"
|
||||||
|
post: Lähetä
|
||||||
|
profile: Profiili
|
||||||
|
profile_settings: Profiiliasetukset
|
||||||
|
reply: Vastaa
|
||||||
|
report: Ilmianna
|
||||||
|
report_notif: "Kiitos ilmiannosta. Moderointitiimimme käsittelee tapauksen mahdollisimman pian."
|
||||||
|
report_notif_remove: "Ilmiantosi on poistettu."
|
||||||
|
reported: Ilmiannettu
|
||||||
|
settings:
|
||||||
|
from_settings_page: "Näet kommenttihistoriasi profiilisivultasi."
|
||||||
|
my_comment_history: "Kommenttihistoriani"
|
||||||
|
profile: Profiili
|
||||||
|
profile_settings: "Profiiliasetukset"
|
||||||
|
sign_in: "Kirjaudu sisään"
|
||||||
|
to_access: "päästäksesi profiilisivulle"
|
||||||
|
user_no_comment: "Et ole jättänyt yhtään kommenttia. Liity keskusteluun!"
|
||||||
|
stream:
|
||||||
|
all_comments: "Kaikki kommentit"
|
||||||
|
temporarily_suspended: "Tilisi on väliaikasesti suljettu, koska et ole noudattanut {0}-sivuston sääntöjä. Voit liittyä keskusteluun uudelleen {1}."
|
||||||
|
comment_not_found: "Kommenttia ei ole olemassa."
|
||||||
|
no_comments: "Ei vielä kommentteja."
|
||||||
|
no_comments_and_closed: "Tässä artikkelissa ei vielä ollut kommentteja."
|
||||||
|
step_1_header: "Ilmianna ongelma"
|
||||||
|
step_2_header: "Kerro ilmiannon syy"
|
||||||
|
step_3_header: "Kiitos panoksestasi"
|
||||||
|
streams:
|
||||||
|
all: Kaikki
|
||||||
|
article: Artikkeli
|
||||||
|
closed: Suljettu
|
||||||
|
empty_result: "Ei hakutuloksia. Kokeile laajentaa hakuasi."
|
||||||
|
filter_streams: "Suodata kommenttivirtoja"
|
||||||
|
newest: Uusin
|
||||||
|
oldest: Vanhin
|
||||||
|
open: Avoin
|
||||||
|
pubdate: "Julkaisupäivä"
|
||||||
|
search: Haku
|
||||||
|
sort_by: "Järjestä"
|
||||||
|
status: "Kommentoinnin tila"
|
||||||
|
stream_status: "Kommentoinnin tila"
|
||||||
|
suspenduser:
|
||||||
|
title_suspend: "Aseta väliaikainen käyttökielto"
|
||||||
|
description_suspend: "Olet asettamassa käyttäjälle {0} väliaikasta käyttökieltoa. Tämä kommentti menee hylätyt-jonoon, ja käyttäjä {0} ei voi reagoida kommentteihin, ilmiantaa, tai vastata niihin, kunnes käyttökielto on päättynyt."
|
||||||
|
select_duration: "Käyttökiellon kesto"
|
||||||
|
one_hour: "1 tunti"
|
||||||
|
hours: "{0} tuntia"
|
||||||
|
days: "{0} päivää"
|
||||||
|
cancel: "Peruuta"
|
||||||
|
suspend_user: "Aseta väliaikainen käyttökielto"
|
||||||
|
email_message_suspend: "Hyvä {0}, tilisi on asetettu {1}-sivuston sääntöjenmukaiseen käyttökieltoon. Et voi osallistua keskusteluun käyttökiellon aikana. Voit liittyä keskusteluun uudelleen {2}."
|
||||||
|
title_notify: "Lähetä käyttäjälle tieto asetetusta käyttökiellosta"
|
||||||
|
notify_suspend_until: "Käyttäjä {0} on asetettu väliaikaiseen käyttökieltoon. Kielto päättyy automaattisesti {1}."
|
||||||
|
description_notify: "Käyttökiellon asettaminen sulkee tilin väliaikaisesti."
|
||||||
|
write_message: "Kirjoita viesti"
|
||||||
|
send: Lähetä
|
||||||
|
reject_username:
|
||||||
|
username: käyttäjänimi
|
||||||
|
no_cancel: "En, peruuta"
|
||||||
|
description_reject: "Haluatko asettaa käyttökiellon, syynä {0}? Jos haluat, asetetaan käyttäjätili väliaikaseen käyttökieltoon, kunnes {0} on kirjoitettu uudelleen."
|
||||||
|
title_notify: "Lähetä käyttäjälle tieto asetetusta käyttökiellosta"
|
||||||
|
description_notify: "Käyttökiellon asettaminen sulkee tilin väliaikaisesti."
|
||||||
|
title_reject: "Huomasimme sinun hylänneen käyttäjänimen"
|
||||||
|
suspend_user: "Aseta väliaikainen käyttökielto"
|
||||||
|
yes_suspend: "Kyllä, sulje väliaikaisesti"
|
||||||
|
email_message_reject: "Toinen yhteisön jäsen on ilmiantanut käyttäjänimesi ja sen perusteella nimi on hylätty. Et voi enää osallistua keskusteluun. Ole ystävällisesti yhteydessä meihin, jos sinulla on asiasta kysyttävää."
|
||||||
|
write_message: "Kirjoita viesti"
|
||||||
|
send: Lähetä
|
||||||
|
thank_you: "Arvostamme palautettasi. Moderaattorimme käy läpi tekemäsi ilmiannon."
|
||||||
|
user:
|
||||||
|
bio_flags: "liputusta kuvaukselle"
|
||||||
|
user_bio: "Käyttäjän kuvaus"
|
||||||
|
username_flags: "liputusta käyttäjänimelle"
|
||||||
|
user_detail:
|
||||||
|
remove_suspension: "Poista käyttökielto"
|
||||||
|
suspend: "Aseta väliaikainen käyttökielto"
|
||||||
|
remove_ban: "Poista kirjoituskielto"
|
||||||
|
ban: "Aseta kirjoituskielto"
|
||||||
|
member_since: "Jäsenenä lähtien"
|
||||||
|
email: "Sähköposti"
|
||||||
|
total_comments: "Kommentteja yhteensä"
|
||||||
|
reject_rate: "Hylkäysaste"
|
||||||
|
reports: "Raportit"
|
||||||
|
all: "Kaikki"
|
||||||
|
rejected: "Hylätyt"
|
||||||
|
account_history: "Tilin historia"
|
||||||
|
user_impersonating: "Käyttäjä on tekeytynyt toiseksi"
|
||||||
|
user_no_comment: "Et ole jättänyt yhtään kommenttia. Liity mukaan keskusteluun!"
|
||||||
|
username_offensive: "Käyttäjänimi on loukkaava"
|
||||||
|
view_conversation: "Näytä keskustelu"
|
||||||
|
install:
|
||||||
|
initial:
|
||||||
|
description: "Ota Talk käyttöön, vain muutama askel jäljellä"
|
||||||
|
submit: "Aloita käyttö"
|
||||||
|
add_organization:
|
||||||
|
description: "Kerro organisaatiosi nimi. Tämä näkyy uusien jäsenten kutsuissa."
|
||||||
|
label: "Organisaation nimi"
|
||||||
|
save: "Tallenna"
|
||||||
|
create:
|
||||||
|
email: "Sähköpostiosoite"
|
||||||
|
username: "Käyttäjänimi"
|
||||||
|
password: "Salasana"
|
||||||
|
confirm_password: "Salasana uudelleen"
|
||||||
|
save: "Tallenna"
|
||||||
|
permitted_domains:
|
||||||
|
title: "Sallitut domainit"
|
||||||
|
description: "Syötä domainit, joilla on lupa käyttää Talkia. Esimerkiksi lokaalikehitys-, QA- ja tuotantoympäristöt: localhost:3000 staging.domain.com domain.com."
|
||||||
|
submit: "Lopeta asennus"
|
||||||
|
final:
|
||||||
|
description: "Kiitos kun asensit Talkin! Lähetämme sähköpostinvarmistusviestin antamaasi osoitteeseen. Voit nyt aloittaa kommentoinnin käytön."
|
||||||
|
launch: "Käynnistä Talk"
|
||||||
|
close: "Sulje asennusnäkymä"
|
||||||
|
admin_sidebar:
|
||||||
|
view_options: "Näytä asetukset"
|
||||||
|
sort_comments: "Järjestä kommentit"
|
||||||
+138
-124
@@ -1,41 +1,42 @@
|
|||||||
fr:
|
fr:
|
||||||
your_account_has_been_suspended: Your account has been temporarily suspended.
|
your_account_has_been_suspended: Votre compte a été temporairement suspendu.
|
||||||
your_account_has_been_banned: Your account has been banned.
|
your_account_has_been_banned: Votre compte a été banni.
|
||||||
your_username_has_been_rejected: Your account has been suspended because your username has been deemed inappropriate. To restore your account please enter a new username.
|
your_username_has_been_rejected: Votre compte a été suspendu en raison de votre nom d’utilisateur jugé inapproprié. Veuillez saisir un nouveau nom d’utilisateur pour restaurer votre compte.
|
||||||
embed_comments_tab: Comments
|
embed_comments_tab: Commentaires
|
||||||
bandialog:
|
bandialog:
|
||||||
are_you_sure: "Êtes-vous sûr de vouloir bannir {0}?"
|
are_you_sure: "Êtes-vous sûr de vouloir bannir {0}?"
|
||||||
ban_user: "Bannir l'utilisateur ?"
|
ban_user: "Bannir l'utilisateur ?"
|
||||||
banned_user: "Utilisateur banni"
|
banned_user: "Utilisateur banni"
|
||||||
cancel: Annuler
|
cancel: Annuler
|
||||||
note: "Remarque: bannir cet utilisateur rejettera également ce commentaire."
|
note: "Remarque : bannir cet utilisateur rejettera également ce commentaire."
|
||||||
note_reject_comment: "Banning this user will also place this comment in the Rejected queue."
|
note_reject_comment: "Bannir cet utilisateur placera ce commentaire dans la liste des commentaires rejetées."
|
||||||
note_ban_user: "Banning this user will not let them comment, react to, or report comments."
|
note_ban_user: "Bannir cet utilisateur empêchera cet utilisateur d’écrire, de réagir à ou de signaler des commentaires."
|
||||||
yes_ban_user: "Oui, bannir cet utilisateur"
|
yes_ban_user: "Oui, bannir cet utilisateur"
|
||||||
write_a_message: "Write a message"
|
write_a_message: "Écrire un message"
|
||||||
send: "Send"
|
send: "Envoyer"
|
||||||
notify_ban_headline: "Notify the user of ban"
|
notify_ban_headline: "Aviser l’utilisateur du bannissement"
|
||||||
notify_ban_description: "This will notify the user by email that they have been banned from the community"
|
notify_ban_description: "Ceci avisera l’utilisateur par courrier électronique de son bannissement de la communauté"
|
||||||
email_message_ban: "Dear {0},\n\nSomeone with access to your account has violated our community guidelines. As a result, your account has been banned. You will no longer be able to comment, like or report comments. if you think this has been done in error, please contact our community team."
|
email_message_ban: "Cher {0},\n\nUne personne ayant accès à votre compte a transgressé nos directives communautaires. En conséquence, votre compte a été banni. Vous ne pourrez plus écrire, aimer ou signaler des commentaires. Si vous pensez qu’il s’agit d’une erreur, veuillez contacter notre équipe communautaire."
|
||||||
bio_offensive: "Cette biographie est offensante"
|
bio_offensive: "Cette biographie est offensante"
|
||||||
cancel: Annuler
|
cancel: "Annuler"
|
||||||
confirm_email:
|
confirm_email:
|
||||||
click_to_confirm: "Click below to confirm your email address"
|
click_to_confirm: "Cliquez ci-dessous pour confirmer votre adresse électronique"
|
||||||
confirm: "Confirm"
|
confirm: "Confirmer"
|
||||||
password_reset:
|
password_reset:
|
||||||
set_new_password: "Change Your Password"
|
mail_sent: 'Si vous avez un compte enregistré, un lien de réinitialisation de mot de passe a été envoyé à cette adresse électronique'
|
||||||
new_password: "New Password"
|
set_new_password: "Changer votre mot de passe"
|
||||||
new_password_help: "Password must be at least 8 characters"
|
new_password: "Nouveau mot de passe"
|
||||||
confirm_new_password: "Confirm New Password"
|
new_password_help: "Le mot de passe doit comporter au moins 8 caractères"
|
||||||
change_password: "Change Password"
|
confirm_new_password: "Confirmer le nouveau mot de passe"
|
||||||
|
change_password: "Changer le mot de passe"
|
||||||
characters_remaining: "caractères restants"
|
characters_remaining: "caractères restants"
|
||||||
comment:
|
comment:
|
||||||
anon: Anonyme
|
anon: "Anonyme"
|
||||||
ban_user: "Bannir utilisateur"
|
ban_user: "Bannir utilisateur"
|
||||||
undo_reject: "Undo"
|
undo_reject: "Annuler"
|
||||||
comment: "Publier un commentaire"
|
comment: "Publier un commentaire"
|
||||||
flagged: signalé
|
flagged: "signalé"
|
||||||
edited: Edited
|
edited: Modifié
|
||||||
view_context: "Afficher le contexte"
|
view_context: "Afficher le contexte"
|
||||||
comment_box:
|
comment_box:
|
||||||
post: "Publier"
|
post: "Publier"
|
||||||
@@ -54,18 +55,18 @@ fr:
|
|||||||
comment_post_notif: "Votre commentaire a été publié."
|
comment_post_notif: "Votre commentaire a été publié."
|
||||||
comment_post_notif_premod: "Merci d'avoir envoyé un commentaire. Notre équipe de modération passera en revue votre commentaire sous peu."
|
comment_post_notif_premod: "Merci d'avoir envoyé un commentaire. Notre équipe de modération passera en revue votre commentaire sous peu."
|
||||||
common:
|
common:
|
||||||
copy: 'Copy'
|
copy: 'Copier'
|
||||||
error: 'An error has occurred.'
|
error: 'Une erreur est survenue.'
|
||||||
reply: 'reply'
|
reply: 'répondre'
|
||||||
replies: 'replies'
|
replies: 'réponses'
|
||||||
reaction: 'reaction'
|
reaction: 'réaction'
|
||||||
reactions: 'reactions'
|
reactions: 'réactions'
|
||||||
story: 'Story'
|
story: 'Histoire'
|
||||||
flagged_usernames:
|
flagged_usernames:
|
||||||
notify_approved: '{0} approved username {1}'
|
notify_approved: "{0} a approuvé le nom d’utilisateur {1}"
|
||||||
notify_rejected: '{0} rejected username {1}'
|
notify_rejected: "{0} a rejeté le nom d’utilisateur {1}"
|
||||||
notify_flagged: '{0} reported username {1}'
|
notify_flagged: "{0} a signalé le nom d’utilisateur {1}"
|
||||||
notify_changed: 'user {0} changed their username to {1}'
|
notify_changed: "l’utilisateur {0} a modifié son nom d’utilisateur en {1}"
|
||||||
community:
|
community:
|
||||||
account_creation_date: "Date de création du compte"
|
account_creation_date: "Date de création du compte"
|
||||||
active: Actif
|
active: Actif
|
||||||
@@ -75,18 +76,18 @@ fr:
|
|||||||
ban_user: "Bannir l'utilisateur ?"
|
ban_user: "Bannir l'utilisateur ?"
|
||||||
banned: Banni
|
banned: Banni
|
||||||
banned_user: "Utilisateur banni"
|
banned_user: "Utilisateur banni"
|
||||||
cancel: Signalé
|
cancel: Annuler
|
||||||
dont_like_username: "Dislike username"
|
dont_like_username: "Ne pas aimer le nom d’utilisateur"
|
||||||
flaggedaccounts: "Noms d'utilisateurs signalés"
|
flaggedaccounts: "Noms d'utilisateurs signalés"
|
||||||
flags: Signalements
|
flags: Signalements
|
||||||
impersonating: Impersonation"
|
impersonating: "Usurpation d’identité"
|
||||||
loading: "Chargement des résultats"
|
loading: "Chargement des résultats"
|
||||||
moderator: Modérateur
|
moderator: Modérateur
|
||||||
newsroom_role: "Rôle de la salle de presse"
|
newsroom_role: "Rôle de la salle de presse"
|
||||||
no_flagged_accounts: "La liste des comptes signalés est vide."
|
no_flagged_accounts: "La liste des comptes signalés est vide."
|
||||||
no_results: "Aucun utilisateur n'a été trouvé avec ce nom d'utilisateur ou cette adresse de messagerie. Ils se cachent !"
|
no_results: "Aucun utilisateur n'a été trouvé avec ce nom d'utilisateur ou cette adresse de messagerie. Ils se cachent !"
|
||||||
offensive: "Offensive"
|
offensive: "Offensive"
|
||||||
other: "Other"
|
other: "Autre"
|
||||||
people: Gens
|
people: Gens
|
||||||
role: "Sélectionnez le rôle ..."
|
role: "Sélectionnez le rôle ..."
|
||||||
select_status: "Sélectionnez l'état ..."
|
select_status: "Sélectionnez l'état ..."
|
||||||
@@ -153,26 +154,33 @@ fr:
|
|||||||
sign_out: "Se Déconnecter"
|
sign_out: "Se Déconnecter"
|
||||||
stories: Histoires
|
stories: Histoires
|
||||||
stream_settings: "Paramètres du fil"
|
stream_settings: "Paramètres du fil"
|
||||||
|
access_message: "Vous devez être un administrateur pour accéder aux paramètres de configuration. Veuillez trouver l'administrateur le plus proche et demandez-lui d'augmenter votre niveau d’accès !"
|
||||||
suspect_word_title: "Liste des mots suspects"
|
suspect_word_title: "Liste des mots suspects"
|
||||||
suspect_word_text: "Les commentaires contenant ces mots ou expressions (non sensibles à la casse) seront mis en évidence dans le flux de commentaires. Tapez un mot et appuyez sur Entrée ou Tabulation pour ajouter. En option, entrez une liste séparée par des virgules."
|
suspect_word_text: "Les commentaires contenant ces mots ou expressions (non sensibles à la casse) seront mis en évidence dans le flux de commentaires. Tapez un mot et appuyez sur Entrée ou Tabulation pour ajouter. En option, entrez une liste séparée par des virgules."
|
||||||
tech_settings: "Paramètres techniques"
|
tech_settings: "Paramètres techniques"
|
||||||
title: "Configurer le fil de commentaires"
|
title: "Configurer le fil de commentaires"
|
||||||
weeks: Semaines
|
weeks: Semaines
|
||||||
wordlist: "Mots interdits"
|
wordlist: "Mots interdits"
|
||||||
|
save_changes_dialog:
|
||||||
|
unsaved_changes: "Modifications non enregistrées"
|
||||||
|
copy: "Vous avez fait une ou plusieurs modifications sans enregistrer. Souhaitez-vous sauvegarder ou abandonner vos modifications ?"
|
||||||
|
save_settings: "Enregistrer la configuration"
|
||||||
|
discard: "Abandonner"
|
||||||
|
cancel: "Annuler"
|
||||||
continue: Continuer
|
continue: Continuer
|
||||||
createdisplay:
|
createdisplay:
|
||||||
check_the_form: "Invalid Form. Please check the fields"
|
check_the_form: "Formulaire invalide. Veuillez vérifier les champs"
|
||||||
continue: "Continue with the same Facebook username"
|
continue: "Continuer avec le même nom d’utilisateur Facebook"
|
||||||
error_create: "Error when changing username"
|
error_create: "Une erreur lors du changement de nom d’utilisateur"
|
||||||
fake_comment_body: "This is an example comment. Readers can share their thoughts and opinions with newsrooms in the comments section."
|
fake_comment_body: "Ceci est un exemple de commentaire. Les lecteurs peuvent livrer leurs réflexions et avis avec les salles de presse dans la section des commentaires."
|
||||||
fake_comment_date: "1 minute ago"
|
fake_comment_date: "il y a 1 minute"
|
||||||
if_you_dont_change_your_name: "If you don't change your username at this step your Facebook display name will appear alongside of all your comments."
|
if_you_dont_change_your_name: "Si vous ne modifiez pas votre nom d’utilisateur à cette étape, votre nom d’affichage Facefook apparaîtra avec tous vos commentaires."
|
||||||
required_field: "Required field"
|
required_field: "Champ obligatoire"
|
||||||
save: Save
|
save: Sauvegarder
|
||||||
special_characters: "Usernames can contain letters numbers and _ only"
|
special_characters: "Les noms d'utilisateur ne peuvent contenir que des lettres, des chiffres et \"_\""
|
||||||
username: Username
|
username: Nom d’utilisateur
|
||||||
write_your_username: "Edit your username"
|
write_your_username: "Modifier votre nom d’utilisateur"
|
||||||
your_username: "Your username appears on every comment you post."
|
your_username: "Votre nom d’utilisateur apparait sur chaque commentaire publié."
|
||||||
done: Terminé
|
done: Terminé
|
||||||
edit_comment:
|
edit_comment:
|
||||||
body_input_label: "Modifier ce commentaire"
|
body_input_label: "Modifier ce commentaire"
|
||||||
@@ -186,47 +194,48 @@ fr:
|
|||||||
minutes_plural: "minutes"
|
minutes_plural: "minutes"
|
||||||
email:
|
email:
|
||||||
suspended:
|
suspended:
|
||||||
subject: "Your account has been suspended"
|
subject: "Votre compte a été suspendu"
|
||||||
banned:
|
banned:
|
||||||
subject: "Your account has been banned"
|
subject: "Votre compte a été banni"
|
||||||
body: "In accordance with The Coral Project’s community guidelines, your account has been banned. You are now longer allowed to comment, flag or engage with our community."
|
body: "Conformément aux directives communautaires du projet Coral, votre compte a été banni. Vous ne pouvez désormais plus commenter, signaler ou collaborer avec notre communauté."
|
||||||
confirm:
|
confirm:
|
||||||
has_been_requested: "A email confirmation has been requested for the following account:"
|
has_been_requested: "Une confirmation de l’adresse électronique a été demandée pour le compte suivant :"
|
||||||
to_confirm: "To confirm the account, please visit the following link:"
|
to_confirm: "Pour confirmer le compte, veuillez suivre le lien suivant :"
|
||||||
confirm_email: "Confirm Email"
|
confirm_email: "Confirmer l’adresse électronique"
|
||||||
if_you_did_not: "If you did not request this, you can safely ignore this email."
|
if_you_did_not: "Si vous n’êtes pas à l’origine de cette requête, vous pouvez ignorer ce courriel en toute sécurité."
|
||||||
subject: "Email Confirmation"
|
subject: "Confirmation adresse électronique"
|
||||||
password_reset:
|
password_reset:
|
||||||
we_received_a_request: "We received a request to reset your password. If you did not request this change, you can ignore this email."
|
we_received_a_request: "Nous avons reçu une demande de réinitialisation de votre mot de passe. Si vous n'avez pas demandé cette modification, vous pouvez ignorer ce courriel."
|
||||||
if_you_did: "If you did,"
|
if_you_did: "Si vous êtes à l’origine de cette requête,"
|
||||||
please_click: "please click here to reset password"
|
please_click: "veuillez cliquez ici pour réinitialiser le mot de passe"
|
||||||
embedlink:
|
embedlink:
|
||||||
copy: "Copier dans le presse-papier"
|
copy: "Copier dans le presse-papier"
|
||||||
error:
|
error:
|
||||||
COMMENT_PARENT_NOT_VISIBLE: "The comment that you're replying to has been removed or doesn't exist."
|
COMMENT_PARENT_NOT_VISIBLE: "Le commentaire auquel vous répondez a été supprimé ou n’existe plus."
|
||||||
EMAIL_VERIFICATION_TOKEN_INVALID: "Email verification token is invalid."
|
EMAIL_VERIFICATION_TOKEN_INVALID: "Le code de vérification de l'adresse électronique n'est pas valide."
|
||||||
PASSWORD_RESET_TOKEN_INVALID: "Your password reset link is invalid."
|
EMAIL_ALREADY_VERIFIED: "Adresse électronique déjà vérifiée."
|
||||||
|
PASSWORD_RESET_TOKEN_INVALID: "Votre lien de réinitialisation de mot de passe n'est pas valide."
|
||||||
COMMENT_TOO_SHORT: "Votre commentaire doit contenir quelque chose"
|
COMMENT_TOO_SHORT: "Votre commentaire doit contenir quelque chose"
|
||||||
NOT_AUTHORIZED: "Vous n'êtes pas autorisé à effectuer cette action."
|
NOT_AUTHORIZED: "Vous n'êtes pas autorisé à effectuer cette action."
|
||||||
NO_SPECIAL_CHARACTERS: "Les noms d'utilisateur ne peuvent contenir que des lettres, des chiffres et \"_\" seulement"
|
NO_SPECIAL_CHARACTERS: "Les noms d'utilisateur ne peuvent contenir que des lettres, des chiffres et \"_\" seulement"
|
||||||
PASSWORD_LENGTH: "Le mot de passe est trop court"
|
PASSWORD_LENGTH: "Le mot de passe est trop court"
|
||||||
PROFANITY_ERROR: "Les noms d'utilisateur ne doivent pas contenir de mots offensants. Veuillez contacter l'administrateur si vous pensez qu'il y a une erreur."
|
PROFANITY_ERROR: "Les noms d'utilisateur ne doivent pas contenir de mots offensants. Veuillez contacter l'administrateur si vous pensez qu'il y a une erreur."
|
||||||
RATE_LIMIT_EXCEEDED: "Rate limit exceeded"
|
RATE_LIMIT_EXCEEDED: "Nombre d’utilisations dépassé"
|
||||||
USERNAME_IN_USE: "Ce nom d'utilisateur est déjà pris"
|
USERNAME_IN_USE: "Ce nom d'utilisateur est déjà pris"
|
||||||
USERNAME_REQUIRED: "Doit entrer un nom d'utilisateur"
|
USERNAME_REQUIRED: "Doit entrer un nom d'utilisateur"
|
||||||
EMAIL_NOT_VERIFIED: "E-mail address not verified"
|
EMAIL_NOT_VERIFIED: "Adresse électronique non vérifiée"
|
||||||
EDIT_WINDOW_ENDED: "Vous ne pouvez plus modifier ce commentaire. La fenêtre de temps pour le faire a expiré."
|
EDIT_WINDOW_ENDED: "Vous ne pouvez plus modifier ce commentaire. La fenêtre de temps pour le faire a expiré."
|
||||||
EDIT_USERNAME_NOT_AUTHORIZED: "Vous n'avez pas la permission de mettre à jour votre nom d'utilisateur."
|
EDIT_USERNAME_NOT_AUTHORIZED: "Vous n'avez pas la permission de mettre à jour votre nom d'utilisateur."
|
||||||
SAME_USERNAME_PROVIDED: "You must submit a different username."
|
SAME_USERNAME_PROVIDED: "Vous devez soumettre un nom d’utilisateur différent."
|
||||||
EMAIL_IN_USE: "Adresse e-mail déjà utilisée"
|
EMAIL_IN_USE: "Adresse électronique déjà utilisée"
|
||||||
EMAIL_REQUIRED: "Une adresse email est requise"
|
EMAIL_REQUIRED: "Une adresse électronique est requise"
|
||||||
LOGIN_MAXIMUM_EXCEEDED: "Vous avez effectué trop de tentatives infructueuses pour entrer votre mot de passe. S'il vous plaît, attendez."
|
LOGIN_MAXIMUM_EXCEEDED: "Vous avez effectué trop de tentatives infructueuses pour entrer votre mot de passe. S'il vous plaît, attendez."
|
||||||
PASSWORD_REQUIRED: "Doit entrer un mot de passe"
|
PASSWORD_REQUIRED: "Doit entrer un mot de passe"
|
||||||
COMMENTING_CLOSED: "Les commentaires sont déjà fermés"
|
COMMENTING_CLOSED: "Les commentaires sont déjà fermés"
|
||||||
NOT_FOUND: "Ressource introuvable"
|
NOT_FOUND: "Ressource introuvable"
|
||||||
ALREADY_EXISTS: "Resource already exists"
|
ALREADY_EXISTS: "Ressource déjà existante"
|
||||||
INVALID_ASSET_URL: "L'URL est invalide"
|
INVALID_ASSET_URL: "L'URL est invalide"
|
||||||
CANNOT_IGNORE_STAFF: "Cannot ignore Staff members."
|
CANNOT_IGNORE_STAFF: "Ne peut pas ignorer les membres de l'équipe."
|
||||||
email: "Pas une adresse e-mail valide"
|
email: "Pas une adresse e-mail valide"
|
||||||
confirm_password: "Les mots de passe ne correspondent pas. Vérifiez à nouveau"
|
confirm_password: "Les mots de passe ne correspondent pas. Vérifiez à nouveau"
|
||||||
network_error: "Échec de connexion au serveur. Vérifiez votre connexion Internet et réessayez."
|
network_error: "Échec de connexion au serveur. Vérifiez votre connexion Internet et réessayez."
|
||||||
@@ -236,20 +245,20 @@ fr:
|
|||||||
password: "Le mot de passe doit être d'au moins 8 caractères"
|
password: "Le mot de passe doit être d'au moins 8 caractères"
|
||||||
username: "Les noms d'utilisateur ne peuvent contenir que des chiffres, des lettres et \"_\""
|
username: "Les noms d'utilisateur ne peuvent contenir que des chiffres, des lettres et \"_\""
|
||||||
required_field: "Ce champ est obligatoire"
|
required_field: "Ce champ est obligatoire"
|
||||||
unexpected: "Unexpected error occurred. Sorry!"
|
unexpected: "Désolé, une erreur inattendue s'est produite."
|
||||||
temporarily_suspended: "Your account is currently suspended. It will be reactivated {0}. Please contact us if you have any questions."
|
temporarily_suspended: "Votre compte est actuellement suspendu. Il sera réactivé {0}. Veuillez nous contacter si vous avez des questions."
|
||||||
flag_comment: "Signaler un commentaire"
|
flag_comment: "Signaler un commentaire"
|
||||||
flag_reason: "Motif du signalement (facultatif)"
|
flag_reason: "Motif du signalement (facultatif)"
|
||||||
flag_username: "Signaler un nom d'utilisateur"
|
flag_username: "Signaler un nom d'utilisateur"
|
||||||
framework:
|
framework:
|
||||||
banned_account_header: "Your account is currently banned."
|
banned_account_header: "Votre compte est actuellement banni."
|
||||||
banned_account_body: "This means that you cannot Like, Report, or write comments."
|
banned_account_body: "Cela signifie que vous ne pouvez pas aimer, signaler ou écrire des commentaires."
|
||||||
comment: commentaire
|
comment: commentaire
|
||||||
comment_is_rejected: "You have rejected this comment."
|
comment_is_rejected: "Vous avez rejeté ce commentaire."
|
||||||
comment_is_hidden: "This comment is not available."
|
comment_is_hidden: "Ce commentaire n’est pas disponible."
|
||||||
comment_is_ignored: "Ce commentaire est caché car vous avez ignoré cet utilisateur."
|
comment_is_ignored: "Ce commentaire est caché car vous avez ignoré cet utilisateur."
|
||||||
comments: commentaires
|
comments: commentaires
|
||||||
configure_stream: "Configure le fil"
|
configure_stream: "Configurer le fil"
|
||||||
content_not_available: "Ce contenu n'est pas disponible"
|
content_not_available: "Ce contenu n'est pas disponible"
|
||||||
edit_name:
|
edit_name:
|
||||||
button: Soumettre
|
button: Soumettre
|
||||||
@@ -257,7 +266,7 @@ fr:
|
|||||||
label: "Nouveau nom d'utilisateur"
|
label: "Nouveau nom d'utilisateur"
|
||||||
msg: "Votre compte est actuellement suspendu car votre nom d'utilisateur a été jugé inapproprié. Pour restaurer votre compte, entrez un nouveau nom d'utilisateur. Contactez-nous si vous avez des questions."
|
msg: "Votre compte est actuellement suspendu car votre nom d'utilisateur a été jugé inapproprié. Pour restaurer votre compte, entrez un nouveau nom d'utilisateur. Contactez-nous si vous avez des questions."
|
||||||
changed_name:
|
changed_name:
|
||||||
msg: "Your username change is under review by our moderation team."
|
msg: "Votre modification de nom d’utilisateur est sous révision par notre équipe de modération."
|
||||||
my_comments: "Mes commentaires"
|
my_comments: "Mes commentaires"
|
||||||
my_profile: "Mon profil"
|
my_profile: "Mon profil"
|
||||||
new_count: "Voir {0} plus {1}"
|
new_count: "Voir {0} plus {1}"
|
||||||
@@ -280,29 +289,29 @@ fr:
|
|||||||
user:
|
user:
|
||||||
username_offensive: "Offensive"
|
username_offensive: "Offensive"
|
||||||
username_nolike: "Dislike"
|
username_nolike: "Dislike"
|
||||||
username_impersonating: "Impersonation"
|
username_impersonating: "Usurpation d’identité"
|
||||||
username_spam: "Spam"
|
username_spam: "Spam"
|
||||||
username_other: "Other"
|
username_other: "Autre"
|
||||||
comment:
|
comment:
|
||||||
comment_offensive: "Offensive"
|
comment_offensive: "Offensive"
|
||||||
comment_spam: "Spam"
|
comment_spam: "Spam"
|
||||||
comment_noagree: "Disagree"
|
comment_noagree: "Pas d’accord"
|
||||||
comment_other: "Other"
|
comment_other: "Autre"
|
||||||
suspect_word: "Suspect Word"
|
suspect_word: "Mot suspect"
|
||||||
banned_word: "Banned Word"
|
banned_word: "Mot banni"
|
||||||
body_count: "Body exceeds max length"
|
body_count: "Le texte dépasse la longueur maximale"
|
||||||
trust: "Trust"
|
trust: "Trust"
|
||||||
links: "Link"
|
links: "Lien"
|
||||||
modqueue:
|
modqueue:
|
||||||
account: "Signalements du compte"
|
account: "Signalements du compte"
|
||||||
actions: Actions
|
actions: Actions
|
||||||
all: tous
|
all: tous
|
||||||
all_streams: "Tous les fils"
|
all_streams: "Tous les fils"
|
||||||
notify_edited: '{0} edited comment "{1}"'
|
notify_edited: '{0} a modifié le commentaire "{1}"'
|
||||||
notify_accepted: '{0} accepted comment "{1}"'
|
notify_accepted: '{0} a accepté le commentaire "{1}"'
|
||||||
notify_rejected: '{0} rejected comment "{1}"'
|
notify_rejected: '{0} a rejeté le commentaire "{1}"'
|
||||||
notify_flagged: '{0} flagged comment "{1}"'
|
notify_flagged: '{0} a signalé le commentaire "{1}"'
|
||||||
notify_reset: '{0} reset status of comment "{1}"'
|
notify_reset: '{0} a réinitialisé le statut du commentaire "{1}"'
|
||||||
approve: "Approuver"
|
approve: "Approuver"
|
||||||
approved: "Approuvé"
|
approved: "Approuvé"
|
||||||
ban_user: "Bannir"
|
ban_user: "Bannir"
|
||||||
@@ -317,26 +326,26 @@ fr:
|
|||||||
mod_faster: "Modérer plus rapidement avec les raccourcis clavier"
|
mod_faster: "Modérer plus rapidement avec les raccourcis clavier"
|
||||||
moderate: "Modérer →"
|
moderate: "Modérer →"
|
||||||
more_detail: "Plus de détails"
|
more_detail: "Plus de détails"
|
||||||
new: New
|
new: Nouveau
|
||||||
newest_first: "Le plus récent d'abord"
|
newest_first: "Le plus récent d'abord"
|
||||||
navigation: Navigation
|
navigation: Navigation
|
||||||
next_comment: "Aller au prochain commentaire"
|
next_comment: "Aller au prochain commentaire"
|
||||||
toggle_search: "Open search"
|
toggle_search: "Ouvrir la recherche"
|
||||||
next_queue: "Switch queues"
|
next_queue: "Changer de file"
|
||||||
oldest_first: "Le plus ancien d'abord"
|
oldest_first: "Le plus ancien d'abord"
|
||||||
premod: Pre-modérer
|
premod: Pre-modérer
|
||||||
prev_comment: "Aller au commentaire précédent"
|
prev_comment: "Aller au commentaire précédent"
|
||||||
reject: "Rejeter"
|
reject: "Rejeter"
|
||||||
rejected: "Rejeté"
|
rejected: "Rejeté"
|
||||||
reply: "Reply"
|
reply: "Répondre"
|
||||||
select_stream: "Sélectionnez le fil"
|
select_stream: "Sélectionnez le fil"
|
||||||
shift_key: ⇧
|
shift_key: ⇧
|
||||||
shortcuts: Raccourcis
|
shortcuts: Raccourcis
|
||||||
sort: "Sort"
|
sort: "Trier"
|
||||||
show_shortcuts: "Afficher les raccourcis"
|
show_shortcuts: "Afficher les raccourcis"
|
||||||
singleview: "Mode zen"
|
singleview: "Mode zen"
|
||||||
thismenu: "Ouvrir ce menu"
|
thismenu: "Ouvrir ce menu"
|
||||||
jump_to_queue: "Jump to specific queue"
|
jump_to_queue: "Aller dans une file d'attente spécifique"
|
||||||
thousand: k
|
thousand: k
|
||||||
try_these: "Essayez ces"
|
try_these: "Essayez ces"
|
||||||
view_more_shortcuts: "Afficher plus de raccourcis"
|
view_more_shortcuts: "Afficher plus de raccourcis"
|
||||||
@@ -345,7 +354,7 @@ fr:
|
|||||||
no_agree_comment: "Je ne suis pas d'accord avec ce commentaire"
|
no_agree_comment: "Je ne suis pas d'accord avec ce commentaire"
|
||||||
no_like_bio: "Je n'aime pas cette biographie"
|
no_like_bio: "Je n'aime pas cette biographie"
|
||||||
no_like_username: "Je n'aime pas ce nom d'utilisateur"
|
no_like_username: "Je n'aime pas ce nom d'utilisateur"
|
||||||
already_flagged_username: "You have already flagged this username."
|
already_flagged_username: "Vous avez déjà signalé ce nom d’utilisateur."
|
||||||
other: Autre
|
other: Autre
|
||||||
permalink: Partager
|
permalink: Partager
|
||||||
personal_info: "Ce commentaire révèle des informations personnelles identifiables"
|
personal_info: "Ce commentaire révèle des informations personnelles identifiables"
|
||||||
@@ -354,9 +363,12 @@ fr:
|
|||||||
profile_settings: "Paramètres"
|
profile_settings: "Paramètres"
|
||||||
reply: Répondre
|
reply: Répondre
|
||||||
report: Signaler
|
report: Signaler
|
||||||
report_notif: "Merci de signaler ce commentaire. Notre équipe de modération a é té informée."
|
report_notif: "Merci de signaler ce commentaire. Notre équipe de modération a été informée."
|
||||||
report_notif_remove: "Votre signalement a été supprimé."
|
report_notif_remove: "Votre signalement a été supprimé."
|
||||||
reported: Signalé
|
reported: Signalé
|
||||||
|
comment_history_blank:
|
||||||
|
title: Vous n’avez écrit aucun commentaire
|
||||||
|
info: Un historique de vos commentaires apparaîtra ici
|
||||||
settings:
|
settings:
|
||||||
from_settings_page: "Dans la page Profil, vous pouvez voir l'historique de vos commentaires."
|
from_settings_page: "Dans la page Profil, vous pouvez voir l'historique de vos commentaires."
|
||||||
my_comment_history: "Mon historique de commentaires"
|
my_comment_history: "Mon historique de commentaires"
|
||||||
@@ -369,8 +381,8 @@ fr:
|
|||||||
all_comments: "Tous les commentaires"
|
all_comments: "Tous les commentaires"
|
||||||
temporarily_suspended: "Conformément à la charte d'utilisation des commentaires de {0}, votre compte a été temporairement suspendu. Merci de revenir dans la conversation {1}."
|
temporarily_suspended: "Conformément à la charte d'utilisation des commentaires de {0}, votre compte a été temporairement suspendu. Merci de revenir dans la conversation {1}."
|
||||||
comment_not_found: "Ce commentaire a été supprimé ou n'existe pas."
|
comment_not_found: "Ce commentaire a été supprimé ou n'existe pas."
|
||||||
no_comments: "There are no comments yet, why don’t you write one?"
|
no_comments: "Il n’y a aucun commentaire pour le moment. Soyez le premier à commenter !"
|
||||||
no_comments_and_closed: "There were no comments on this article."
|
no_comments_and_closed: "Il n'y avait aucun commentaire sur cet article."
|
||||||
step_1_header: "Signaler un problème"
|
step_1_header: "Signaler un problème"
|
||||||
step_2_header: "Aidez-nous à comprendre"
|
step_2_header: "Aidez-nous à comprendre"
|
||||||
step_3_header: "Merci pour votre participation"
|
step_3_header: "Merci pour votre participation"
|
||||||
@@ -395,6 +407,8 @@ fr:
|
|||||||
one_hour: "1 heure"
|
one_hour: "1 heure"
|
||||||
hours: "{0} heures"
|
hours: "{0} heures"
|
||||||
days: "{0} jours"
|
days: "{0} jours"
|
||||||
|
hour: "{0} heures"
|
||||||
|
day: "{0} jours"
|
||||||
cancel: "Annuler"
|
cancel: "Annuler"
|
||||||
suspend_user: "Suspendre l'utilisateur"
|
suspend_user: "Suspendre l'utilisateur"
|
||||||
email_message_suspend: "Cher {0},\n\nConformément à la charte des commentaires de {1}, votre compte a été temporairement suspendu. Pendant cette période, vous ne pourrez pas commenter, signaler ou participer à d'autres commentaires. \n\nMerci de revenir dans la conversation {2}."
|
email_message_suspend: "Cher {0},\n\nConformément à la charte des commentaires de {1}, votre compte a été temporairement suspendu. Pendant cette période, vous ne pourrez pas commenter, signaler ou participer à d'autres commentaires. \n\nMerci de revenir dans la conversation {2}."
|
||||||
@@ -421,28 +435,28 @@ fr:
|
|||||||
user_bio: "Bio de l'utilisateur"
|
user_bio: "Bio de l'utilisateur"
|
||||||
username_flags: "Signaler pour ce nom d'utilisateur"
|
username_flags: "Signaler pour ce nom d'utilisateur"
|
||||||
user_detail:
|
user_detail:
|
||||||
remove_suspension: "Remove Suspension"
|
remove_suspension: "Lever la suspension"
|
||||||
suspend: "Suspend User"
|
suspend: "Suspendre l’utilisateur"
|
||||||
remove_ban: "Remove Ban"
|
remove_ban: "Lever le bannissement"
|
||||||
ban: "Ban User"
|
ban: "Bannir l’utilisateur"
|
||||||
member_since: "Member Since"
|
member_since: "Membre depuis"
|
||||||
email: "Email"
|
email: "adresse électronique"
|
||||||
total_comments: "Total Comments"
|
total_comments: "Nombre total de commentaires"
|
||||||
reject_rate: "Reject Rate"
|
reject_rate: "Fréquence de rejet"
|
||||||
reports: "Reports"
|
reports: "Signalements"
|
||||||
all: "All"
|
all: "Tous"
|
||||||
rejected: "Rejected"
|
rejected: "Rejeté"
|
||||||
account_history: "Account History"
|
account_history: "Historique de compte"
|
||||||
account_history:
|
account_history:
|
||||||
user_banned: "User banned"
|
user_banned: "Utilisateur banni"
|
||||||
ban_removed: "Ban removed"
|
ban_removed: "Bannissement levé"
|
||||||
username_status: "Username {0}"
|
username_status: "Nom d’utilisateur {0}"
|
||||||
suspended: "Suspended, {0}"
|
suspended: "Suspendu, {0}"
|
||||||
suspension_removed: "Suspension removed"
|
suspension_removed: "Suspension levée"
|
||||||
system: "System"
|
system: "Système"
|
||||||
date: "Date"
|
date: "Date"
|
||||||
action: "Action"
|
action: "Action"
|
||||||
taken_by: "Taken By"
|
taken_by: "Prise par"
|
||||||
user_impersonating: "Cet utilisateur se fait passer pour quelqu'un d'autre"
|
user_impersonating: "Cet utilisateur se fait passer pour quelqu'un d'autre"
|
||||||
user_no_comment: "Vous n'avez jamais laissé de commentaire. Rejoignez la conversation !"
|
user_no_comment: "Vous n'avez jamais laissé de commentaire. Rejoignez la conversation !"
|
||||||
username_offensive: "Ce nom d'utilisateur est offensant"
|
username_offensive: "Ce nom d'utilisateur est offensant"
|
||||||
@@ -471,5 +485,5 @@ fr:
|
|||||||
launch: "Lancer Talk"
|
launch: "Lancer Talk"
|
||||||
close: "Fermez cet installateur"
|
close: "Fermez cet installateur"
|
||||||
admin_sidebar:
|
admin_sidebar:
|
||||||
view_options: "View Options"
|
view_options: "Afficher les options"
|
||||||
sort_comments: "Sort Comments"
|
sort_comments: "Trier les commentaires"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const authorization = (module.exports = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const debug = require('debug')('talk:middleware:authorization');
|
const debug = require('debug')('talk:middleware:authorization');
|
||||||
const ErrNotAuthorized = require('../errors').ErrNotAuthorized;
|
const { ErrNotAuthorized } = require('../errors');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* has returns true if the user has at least one of the roles specified,
|
* has returns true if the user has at least one of the roles specified,
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
const { logger } = require('../services/logging');
|
||||||
|
const now = require('performance-now');
|
||||||
|
|
||||||
|
const log = (req, res, next) => {
|
||||||
|
const startTime = now();
|
||||||
|
const end = res.end;
|
||||||
|
res.end = function(chunk, encoding) {
|
||||||
|
// Compute the end time.
|
||||||
|
const responseTime = Math.round(now() - startTime);
|
||||||
|
|
||||||
|
// Get some extra goodies from the request.
|
||||||
|
const userAgent = req.get('User-Agent');
|
||||||
|
|
||||||
|
// Reattach the old end, and finish.
|
||||||
|
res.end = end;
|
||||||
|
res.end(chunk, encoding);
|
||||||
|
|
||||||
|
// Log this out.
|
||||||
|
logger.info(
|
||||||
|
{
|
||||||
|
traceID: req.id,
|
||||||
|
url: req.originalUrl || req.url,
|
||||||
|
method: req.method,
|
||||||
|
statusCode: res.statusCode,
|
||||||
|
userAgent,
|
||||||
|
responseTime,
|
||||||
|
},
|
||||||
|
'http request'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
||||||
|
const error = (err, req, res, next) => {
|
||||||
|
logger.error({ err }, 'http error');
|
||||||
|
next(err);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { log, error };
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
const uuid = require('uuid/v1');
|
||||||
|
|
||||||
|
// Trace middleware attaches a request id to each incoming request.
|
||||||
|
module.exports = (req, res, next) => {
|
||||||
|
req.id = uuid();
|
||||||
|
next();
|
||||||
|
};
|
||||||
+3
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "talk",
|
"name": "talk",
|
||||||
"version": "4.3.0",
|
"version": "4.3.1",
|
||||||
"description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net",
|
"description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net",
|
||||||
"main": "app.js",
|
"main": "app.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
@@ -146,7 +146,6 @@
|
|||||||
"minimist": "^1.2.0",
|
"minimist": "^1.2.0",
|
||||||
"moment": "^2.18.1",
|
"moment": "^2.18.1",
|
||||||
"mongoose": "^4.12.3",
|
"mongoose": "^4.12.3",
|
||||||
"morgan": "^1.9.0",
|
|
||||||
"ms": "^2.0.0",
|
"ms": "^2.0.0",
|
||||||
"murmurhash-js": "^1.0.0",
|
"murmurhash-js": "^1.0.0",
|
||||||
"name-all-modules-plugin": "^1.0.1",
|
"name-all-modules-plugin": "^1.0.1",
|
||||||
@@ -158,6 +157,7 @@
|
|||||||
"passport": "^0.4.0",
|
"passport": "^0.4.0",
|
||||||
"passport-jwt": "^3.0.0",
|
"passport-jwt": "^3.0.0",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
|
"performance-now": "^2.1.0",
|
||||||
"pluralize": "^7.0.0",
|
"pluralize": "^7.0.0",
|
||||||
"postcss-loader": "^1.3.3",
|
"postcss-loader": "^1.3.3",
|
||||||
"postcss-smart-import": "^0.5.1",
|
"postcss-smart-import": "^0.5.1",
|
||||||
@@ -215,6 +215,7 @@
|
|||||||
"babel-plugin-dynamic-import-node": "^1.1.0",
|
"babel-plugin-dynamic-import-node": "^1.1.0",
|
||||||
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
|
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
|
||||||
"browserstack-local": "^1.3.0",
|
"browserstack-local": "^1.3.0",
|
||||||
|
"bunyan-debug-stream": "^1.0.8",
|
||||||
"chai": "^3.5.0",
|
"chai": "^3.5.0",
|
||||||
"chai-as-promised": "^6.0.0",
|
"chai-as-promised": "^6.0.0",
|
||||||
"chai-datetime": "^1.5.0",
|
"chai-datetime": "^1.5.0",
|
||||||
|
|||||||
@@ -10,4 +10,5 @@ module.exports = {
|
|||||||
LIST_OWN_TOKENS: 'LIST_OWN_TOKENS',
|
LIST_OWN_TOKENS: 'LIST_OWN_TOKENS',
|
||||||
VIEW_USER_ROLE: 'VIEW_USER_ROLE',
|
VIEW_USER_ROLE: 'VIEW_USER_ROLE',
|
||||||
VIEW_USER_EMAIL: 'VIEW_USER_EMAIL',
|
VIEW_USER_EMAIL: 'VIEW_USER_EMAIL',
|
||||||
|
VIEW_BODY_HISTORY: 'VIEW_BODY_HISTORY',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ module.exports = (user, perm) => {
|
|||||||
case types.VIEW_PROTECTED_SETTINGS:
|
case types.VIEW_PROTECTED_SETTINGS:
|
||||||
case types.VIEW_USER_ROLE:
|
case types.VIEW_USER_ROLE:
|
||||||
case types.VIEW_USER_EMAIL:
|
case types.VIEW_USER_EMAIL:
|
||||||
|
case types.VIEW_BODY_HISTORY:
|
||||||
return check(user, ['ADMIN', 'MODERATOR']);
|
return check(user, ['ADMIN', 'MODERATOR']);
|
||||||
case types.LIST_OWN_TOKENS:
|
case types.LIST_OWN_TOKENS:
|
||||||
return check(user, ['ADMIN']);
|
return check(user, ['ADMIN']);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const { SEARCH_OTHER_USERS } = require('../../../perms/constants');
|
const { SEARCH_OTHER_USERS } = require('../../../perms/constants');
|
||||||
const errors = require('../../../errors');
|
const { ErrNotFound, ErrAlreadyExists } = require('../../../errors');
|
||||||
const pluralize = require('pluralize');
|
const pluralize = require('pluralize');
|
||||||
const sc = require('snake-case');
|
const sc = require('snake-case');
|
||||||
const CommentModel = require('../../../models/comment');
|
const CommentModel = require('../../../models/comment');
|
||||||
@@ -192,7 +192,7 @@ function getReactionConfig(reaction) {
|
|||||||
) => {
|
) => {
|
||||||
const comment = await Comments.get.load(item_id);
|
const comment = await Comments.get.load(item_id);
|
||||||
if (!comment) {
|
if (!comment) {
|
||||||
throw errors.ErrNotFound;
|
throw new ErrNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -211,7 +211,7 @@ function getReactionConfig(reaction) {
|
|||||||
[reaction]: action,
|
[reaction]: action,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof errors.ErrAlreadyExists) {
|
if (err instanceof ErrAlreadyExists) {
|
||||||
return err.metadata.existing;
|
return err.metadata.existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
const { APIError } = require('errors');
|
const { TalkError } = require('errors');
|
||||||
|
|
||||||
// ErrSpam is sent during a `CreateComment` mutation where
|
// ErrSpam is sent during a `CreateComment` mutation where
|
||||||
// `input.checkSpam` is set to true and the comment contains
|
// `input.checkSpam` is set to true and the comment contains
|
||||||
// detected spam as determined by the akismet service.
|
// detected spam as determined by the akismet service.
|
||||||
const ErrSpam = new APIError('Comment is spam', {
|
class ErrSpam extends TalkError {
|
||||||
status: 400,
|
constructor() {
|
||||||
translation_key: 'COMMENT_IS_SPAM',
|
super('Comment is spam', {
|
||||||
});
|
status: 400,
|
||||||
|
translation_key: 'COMMENT_IS_SPAM',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ErrSpam,
|
ErrSpam,
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ module.exports = {
|
|||||||
|
|
||||||
if (spam) {
|
if (spam) {
|
||||||
if (input.checkSpam) {
|
if (input.checkSpam) {
|
||||||
throw ErrSpam;
|
throw new ErrSpam();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach reason information for the flag being added.
|
// Attach reason information for the flag being added.
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ class SignUp extends React.Component {
|
|||||||
showErrors={!!emailError}
|
showErrors={!!emailError}
|
||||||
errorMsg={emailError}
|
errorMsg={emailError}
|
||||||
onChange={this.handleEmailChange}
|
onChange={this.handleEmailChange}
|
||||||
|
autocomplete="off"
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
id="username"
|
id="username"
|
||||||
@@ -85,6 +86,8 @@ class SignUp extends React.Component {
|
|||||||
showErrors={!!usernameError}
|
showErrors={!!usernameError}
|
||||||
errorMsg={usernameError}
|
errorMsg={usernameError}
|
||||||
onChange={this.handleUsernameChange}
|
onChange={this.handleUsernameChange}
|
||||||
|
autocomplete="off"
|
||||||
|
autocapitalize="none"
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
id="password"
|
id="password"
|
||||||
@@ -96,6 +99,7 @@ class SignUp extends React.Component {
|
|||||||
errorMsg={passwordError}
|
errorMsg={passwordError}
|
||||||
onChange={this.handlePasswordChange}
|
onChange={this.handlePasswordChange}
|
||||||
minLength="8"
|
minLength="8"
|
||||||
|
autocomplete="off"
|
||||||
/>
|
/>
|
||||||
{passwordError && (
|
{passwordError && (
|
||||||
<span className={styles.hint}>
|
<span className={styles.hint}>
|
||||||
@@ -113,6 +117,7 @@ class SignUp extends React.Component {
|
|||||||
errorMsg={passwordRepeatError}
|
errorMsg={passwordRepeatError}
|
||||||
onChange={this.handlePasswordRepeatChange}
|
onChange={this.handlePasswordRepeatChange}
|
||||||
minLength="8"
|
minLength="8"
|
||||||
|
autocomplete="off"
|
||||||
/>
|
/>
|
||||||
<Slot
|
<Slot
|
||||||
fill="talkPluginAuth.formField"
|
fill="talkPluginAuth.formField"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const { get } = require('lodash');
|
const { get, map } = require('lodash');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const handle = async (ctx, comment) => {
|
const handle = async (ctx, comment) => {
|
||||||
@@ -23,6 +23,9 @@ const handle = async (ctx, comment) => {
|
|||||||
id
|
id
|
||||||
user {
|
user {
|
||||||
id
|
id
|
||||||
|
ignoredUsers {
|
||||||
|
id
|
||||||
|
}
|
||||||
notificationSettings {
|
notificationSettings {
|
||||||
onReply
|
onReply
|
||||||
}
|
}
|
||||||
@@ -53,13 +56,23 @@ const handle = async (ctx, comment) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pull out the author of the new comment.
|
||||||
|
const authorID = get(comment, 'author_id');
|
||||||
|
|
||||||
// Check to see if this is yourself replying to yourself, if that's the case
|
// Check to see if this is yourself replying to yourself, if that's the case
|
||||||
// don't send a notification.
|
// don't send a notification.
|
||||||
if (userID === get(comment, 'author_id')) {
|
if (userID === authorID) {
|
||||||
ctx.log.info('user id of parent comment is the same as the new comment');
|
ctx.log.info('user id of parent comment is the same as the new comment');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check to see if this user is ignoring the user who replied to their
|
||||||
|
// comment.
|
||||||
|
if (map(get(comment, 'user.ignoredUsers', []), 'id').indexOf(authorID)) {
|
||||||
|
ctx.log.info('parent user has ignored the author of the new comment');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// The user does have notifications for replied comments enabled, queue the
|
// The user does have notifications for replied comments enabled, queue the
|
||||||
// notification to be sent.
|
// notification to be sent.
|
||||||
return { userID, date: comment.created_at, context: comment.id };
|
return { userID, date: comment.created_at, context: comment.id };
|
||||||
|
|||||||
@@ -29,10 +29,11 @@ async function updateNotificationSettings(ctx, settings) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ctx => {
|
module.exports = ctx => {
|
||||||
|
const { connectors: { errors: ErrNotAuthorized } } = ctx;
|
||||||
|
|
||||||
let mutators = {
|
let mutators = {
|
||||||
User: {
|
User: {
|
||||||
updateNotificationSettings: () =>
|
updateNotificationSettings: () => Promise.reject(new ErrNotAuthorized()),
|
||||||
Promise.reject(ctx.connectors.errors.ErrNotAuthorized),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
const { APIError } = require('errors');
|
const { TalkError } = require('errors');
|
||||||
|
|
||||||
// ErrToxic is sent during a `CreateComment` mutation where
|
// ErrToxic is sent during a `CreateComment` mutation where
|
||||||
// `input.checkToxicity` is set to true and the comment contains
|
// `input.checkToxicity` is set to true and the comment contains
|
||||||
// toxic language as determined by the perspective service.
|
// toxic language as determined by the perspective service.
|
||||||
const ErrToxic = new APIError('Comment is toxic', {
|
class ErrToxic extends TalkError {
|
||||||
status: 400,
|
constructor() {
|
||||||
translation_key: 'COMMENT_IS_TOXIC',
|
super('Comment is toxic', {
|
||||||
});
|
status: 400,
|
||||||
|
translation_key: 'COMMENT_IS_TOXIC',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ErrToxic,
|
ErrToxic,
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ module.exports = {
|
|||||||
|
|
||||||
if (isToxic(scores)) {
|
if (isToxic(scores)) {
|
||||||
if (input.checkToxicity) {
|
if (input.checkToxicity) {
|
||||||
throw ErrToxic;
|
throw new ErrToxic();
|
||||||
}
|
}
|
||||||
|
|
||||||
input.status = 'SYSTEM_WITHHELD';
|
input.status = 'SYSTEM_WITHHELD';
|
||||||
|
|||||||
+5
-10
@@ -1,7 +1,7 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const UsersService = require('../../../services/users');
|
const UsersService = require('../../../services/users');
|
||||||
const errors = require('../../../errors');
|
const { ErrMissingEmail, ErrNotFound } = require('../../../errors');
|
||||||
const authorization = require('../../../middleware/authorization');
|
const authorization = require('../../../middleware/authorization');
|
||||||
const Limit = require('../../../services/limit');
|
const Limit = require('../../../services/limit');
|
||||||
|
|
||||||
@@ -40,17 +40,12 @@ router.post('/resend-verify', async (req, res, next) => {
|
|||||||
// Clean up and validate the email.
|
// Clean up and validate the email.
|
||||||
email = email.toLowerCase().trim();
|
email = email.toLowerCase().trim();
|
||||||
if (email.length < 5) {
|
if (email.length < 5) {
|
||||||
return next(errors.ErrMissingEmail);
|
return next(new ErrMissingEmail());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we're past the rate limit, if we are, stop now. Otherwise, record
|
// Check if we're past the rate limit, if we are, stop now. Otherwise, record
|
||||||
// this as an attempt to send a verification email.
|
// this as an attempt to send a verification email.
|
||||||
try {
|
try {
|
||||||
const tries = await resendRateLimiter.get(email);
|
|
||||||
if (tries > 0) {
|
|
||||||
throw errors.ErrMaxRateLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
await resendRateLimiter.test(email);
|
await resendRateLimiter.test(email);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return next(err);
|
return next(err);
|
||||||
@@ -59,7 +54,7 @@ router.post('/resend-verify', async (req, res, next) => {
|
|||||||
try {
|
try {
|
||||||
const user = await UsersService.findLocalUser(email);
|
const user = await UsersService.findLocalUser(email);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw errors.ErrNotFound;
|
throw new ErrNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
await UsersService.sendEmailConfirmation(user, email, redirectUri);
|
await UsersService.sendEmailConfirmation(user, email, redirectUri);
|
||||||
@@ -81,13 +76,13 @@ router.post(
|
|||||||
try {
|
try {
|
||||||
let user = await UsersService.findById(user_id);
|
let user = await UsersService.findById(user_id);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return next(errors.ErrNotFound);
|
return next(new ErrNotFound());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the first local profile.
|
// Find the first local profile.
|
||||||
const email = user.firstEmail;
|
const email = user.firstEmail;
|
||||||
if (!email) {
|
if (!email) {
|
||||||
return next(errors.ErrMissingEmail);
|
return next(new ErrMissingEmail());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the email to the first local profile that was found.
|
// Send the email to the first local profile that was found.
|
||||||
|
|||||||
+8
-15
@@ -1,8 +1,8 @@
|
|||||||
const SetupService = require('../services/setup');
|
const SetupService = require('../services/setup');
|
||||||
const authentication = require('../middleware/authentication');
|
const authentication = require('../middleware/authentication');
|
||||||
|
const logging = require('../middleware/logging');
|
||||||
const cookieParser = require('cookie-parser');
|
const cookieParser = require('cookie-parser');
|
||||||
const enabled = require('debug').enabled;
|
const { TalkError, ErrNotFound } = require('../errors');
|
||||||
const errors = require('../errors');
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const i18n = require('../middleware/i18n');
|
const i18n = require('../middleware/i18n');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
@@ -149,19 +149,16 @@ router.use(require('./plugins'));
|
|||||||
|
|
||||||
// Catch 404 and forward to error handler.
|
// Catch 404 and forward to error handler.
|
||||||
router.use((req, res, next) => {
|
router.use((req, res, next) => {
|
||||||
next(errors.ErrNotFound);
|
next(new ErrNotFound());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add logging for errors.
|
||||||
|
router.use(logging.error);
|
||||||
|
|
||||||
// General API error handler. Respond with the message and error if we have it
|
// General API error handler. Respond with the message and error if we have it
|
||||||
// while returning a status code that makes sense.
|
// while returning a status code that makes sense.
|
||||||
router.use('/api', (err, req, res, next) => {
|
router.use('/api', (err, req, res, next) => {
|
||||||
if (err !== errors.ErrNotFound) {
|
if (err instanceof TalkError) {
|
||||||
if (process.env.NODE_ENV !== 'test' || enabled('talk:errors')) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err instanceof errors.APIError) {
|
|
||||||
res.status(err.status).json({
|
res.status(err.status).json({
|
||||||
message: res.locals.t(`error.${err.translation_key}`),
|
message: res.locals.t(`error.${err.translation_key}`),
|
||||||
error: err,
|
error: err,
|
||||||
@@ -172,11 +169,7 @@ router.use('/api', (err, req, res, next) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.use('/', (err, req, res, next) => {
|
router.use('/', (err, req, res, next) => {
|
||||||
if (err !== errors.ErrNotFound) {
|
if (err instanceof TalkError) {
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err instanceof errors.APIError) {
|
|
||||||
res.status(err.status);
|
res.status(err.status);
|
||||||
res.render('error', {
|
res.render('error', {
|
||||||
message: res.locals.t(`error.${err.translation_key}`),
|
message: res.locals.t(`error.${err.translation_key}`),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const app = require('./app');
|
const app = require('./app');
|
||||||
const errors = require('./errors');
|
const { ErrSettingsInit, ErrInstallLock } = require('./errors');
|
||||||
const { createServer } = require('http');
|
const { createServer } = require('http');
|
||||||
const jobs = require('./jobs');
|
const jobs = require('./jobs');
|
||||||
const MigrationService = require('./services/migration');
|
const MigrationService = require('./services/migration');
|
||||||
@@ -95,20 +95,16 @@ async function serve({
|
|||||||
await SetupService.isAvailable();
|
await SetupService.isAvailable();
|
||||||
|
|
||||||
logger.info('Setup is currently available, migrations not being checked');
|
logger.info('Setup is currently available, migrations not being checked');
|
||||||
} catch (e) {
|
} catch (err) {
|
||||||
// Check the error.
|
// Check the error.
|
||||||
switch (e) {
|
if (err instanceof ErrInstallLock || err instanceof ErrSettingsInit) {
|
||||||
case errors.ErrInstallLock:
|
// The error was expected, just continue.
|
||||||
case errors.ErrSettingsInit:
|
logger.info(
|
||||||
logger.info(
|
'Setup is not currently available, migrations now being checked'
|
||||||
'Setup is not currently available, migrations now being checked'
|
);
|
||||||
);
|
} else {
|
||||||
|
// The error was not expected, throw the error!
|
||||||
// The error was expected, just continue.
|
throw err;
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// The error was not expected, throw the error!
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now try and check the migration status.
|
// Now try and check the migration status.
|
||||||
|
|||||||
+9
-6
@@ -2,9 +2,12 @@ const CommentModel = require('../models/comment');
|
|||||||
const AssetModel = require('../models/asset');
|
const AssetModel = require('../models/asset');
|
||||||
const SettingsService = require('./settings');
|
const SettingsService = require('./settings');
|
||||||
const DomainList = require('./domain_list');
|
const DomainList = require('./domain_list');
|
||||||
const errors = require('../errors');
|
const {
|
||||||
const merge = require('lodash/merge');
|
ErrAssetURLAlreadyExists,
|
||||||
const isEmpty = require('lodash/isEmpty');
|
ErrNotFound,
|
||||||
|
ErrInvalidAssetURL,
|
||||||
|
} = require('../errors');
|
||||||
|
const { merge, isEmpty } = require('lodash');
|
||||||
const { dotize } = require('./utils');
|
const { dotize } = require('./utils');
|
||||||
|
|
||||||
module.exports = class AssetsService {
|
module.exports = class AssetsService {
|
||||||
@@ -73,7 +76,7 @@ module.exports = class AssetsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!whitelisted) {
|
if (!whitelisted) {
|
||||||
return Promise.reject(errors.ErrInvalidAssetURL);
|
throw new ErrInvalidAssetURL(url);
|
||||||
} else {
|
} else {
|
||||||
return AssetModel.findOneAndUpdate({ url }, update, {
|
return AssetModel.findOneAndUpdate({ url }, update, {
|
||||||
// Ensure that if it's new, we return the new object created.
|
// Ensure that if it's new, we return the new object created.
|
||||||
@@ -211,7 +214,7 @@ module.exports = class AssetsService {
|
|||||||
// Try to see if an asset already exists with the given url.
|
// Try to see if an asset already exists with the given url.
|
||||||
let asset = await AssetsService.findByUrl(url);
|
let asset = await AssetsService.findByUrl(url);
|
||||||
if (asset !== null) {
|
if (asset !== null) {
|
||||||
throw errors.ErrAssetURLAlreadyExists;
|
throw new ErrAssetURLAlreadyExists();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seems that there was no other asset with the same url, try and perform
|
// Seems that there was no other asset with the same url, try and perform
|
||||||
@@ -227,7 +230,7 @@ module.exports = class AssetsService {
|
|||||||
dstAssetID,
|
dstAssetID,
|
||||||
]);
|
]);
|
||||||
if (!srcAsset || !dstAsset) {
|
if (!srcAsset || !dstAsset) {
|
||||||
throw errors.ErrNotFound;
|
throw new ErrNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve the merge operation, this invloves moving all resources attached
|
// Resolve the merge operation, this invloves moving all resources attached
|
||||||
|
|||||||
+13
-10
@@ -2,10 +2,13 @@ const CommentModel = require('../models/comment');
|
|||||||
const { dotize } = require('./utils');
|
const { dotize } = require('./utils');
|
||||||
const debug = require('debug')('talk:services:comments');
|
const debug = require('debug')('talk:services:comments');
|
||||||
const SettingsService = require('./settings');
|
const SettingsService = require('./settings');
|
||||||
|
const { merge, cloneDeep } = require('lodash');
|
||||||
const cloneDeep = require('lodash/cloneDeep');
|
const {
|
||||||
const errors = require('../errors');
|
ErrParentDoesNotVisible,
|
||||||
const merge = require('lodash/merge');
|
ErrNotFound,
|
||||||
|
ErrNotAuthorized,
|
||||||
|
ErrEditWindowHasEnded,
|
||||||
|
} = require('../errors');
|
||||||
|
|
||||||
const incrReplyCount = async (comment, value) => {
|
const incrReplyCount = async (comment, value) => {
|
||||||
try {
|
try {
|
||||||
@@ -40,7 +43,7 @@ module.exports = {
|
|||||||
if (parent_id !== null) {
|
if (parent_id !== null) {
|
||||||
const parent = await CommentModel.findOne({ id: parent_id });
|
const parent = await CommentModel.findOne({ id: parent_id });
|
||||||
if (parent === null || !parent.visible) {
|
if (parent === null || !parent.visible) {
|
||||||
throw errors.ErrParentDoesNotVisible;
|
throw new ErrParentDoesNotVisible();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +129,7 @@ module.exports = {
|
|||||||
const comment = await CommentModel.findOne({ id });
|
const comment = await CommentModel.findOne({ id });
|
||||||
if (comment == null) {
|
if (comment == null) {
|
||||||
debug('rejecting comment edit because comment was not found');
|
debug('rejecting comment edit because comment was not found');
|
||||||
throw errors.ErrNotFound;
|
throw new ErrNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to see if the user was't allowed to edit it.
|
// Check to see if the user was't allowed to edit it.
|
||||||
@@ -134,7 +137,7 @@ module.exports = {
|
|||||||
debug(
|
debug(
|
||||||
'rejecting comment edit because author id does not match editing user'
|
'rejecting comment edit because author id does not match editing user'
|
||||||
);
|
);
|
||||||
throw errors.ErrNotAuthorized;
|
throw new ErrNotAuthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to see if the comment had a status that was editable.
|
// Check to see if the comment had a status that was editable.
|
||||||
@@ -142,13 +145,13 @@ module.exports = {
|
|||||||
debug(
|
debug(
|
||||||
'rejecting comment edit because original comment has a non-editable status'
|
'rejecting comment edit because original comment has a non-editable status'
|
||||||
);
|
);
|
||||||
throw errors.ErrNotAuthorized;
|
throw new ErrNotAuthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check to see if the edit window expired.
|
// Check to see if the edit window expired.
|
||||||
if (comment.created_at <= lastEditableCommentCreatedAt) {
|
if (comment.created_at <= lastEditableCommentCreatedAt) {
|
||||||
debug('rejecting comment edit because outside edit time window');
|
debug('rejecting comment edit because outside edit time window');
|
||||||
throw errors.ErrEditWindowHasEnded;
|
throw new ErrEditWindowHasEnded();
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('comment edit failed for an unexpected reason');
|
throw new Error('comment edit failed for an unexpected reason');
|
||||||
@@ -198,7 +201,7 @@ module.exports = {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (originalComment == null) {
|
if (originalComment == null) {
|
||||||
throw errors.ErrNotFound;
|
throw new ErrNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
const editedComment = new CommentModel(originalComment.toObject());
|
const editedComment = new CommentModel(originalComment.toObject());
|
||||||
|
|||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
const ms = require('ms');
|
const ms = require('ms');
|
||||||
const errors = require('../errors');
|
const { ErrMaxRateLimit } = require('../errors');
|
||||||
const { createClientFactory } = require('./redis');
|
const { createClientFactory } = require('./redis');
|
||||||
const client = createClientFactory();
|
const client = createClientFactory();
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ class Limit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (tries > this.max) {
|
if (tries > this.max) {
|
||||||
throw errors.ErrMaxRateLimit;
|
throw new ErrMaxRateLimit(this.max, tries);
|
||||||
}
|
}
|
||||||
|
|
||||||
return tries;
|
return tries;
|
||||||
|
|||||||
+35
-7
@@ -1,18 +1,46 @@
|
|||||||
const { version } = require('../package.json');
|
const { version } = require('../package.json');
|
||||||
const Logger = require('bunyan');
|
const path = require('path');
|
||||||
|
const { createLogger: createBunyanLogger, stdSerializers } = require('bunyan');
|
||||||
const { LOGGING_LEVEL, REVISION_HASH } = require('../config');
|
const { LOGGING_LEVEL, REVISION_HASH } = require('../config');
|
||||||
const logger = new Logger({
|
|
||||||
|
// Streams enables the ability for development logs to be readable to a human,
|
||||||
|
// but will send JSON logs in production that's parsable by a system like ELK.
|
||||||
|
const streams = (() => {
|
||||||
|
// In development, use the debug stream printer.
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
const debug = require('bunyan-debug-stream');
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
level: 'debug',
|
||||||
|
type: 'raw',
|
||||||
|
stream: debug({
|
||||||
|
basepath: path.resolve(__dirname, '..'),
|
||||||
|
forceColor: true,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// In production, emit JSON.
|
||||||
|
return [{ stream: process.stdout, level: 'info' }];
|
||||||
|
})();
|
||||||
|
|
||||||
|
// logger is the base logger used by all logging systems in Talk.
|
||||||
|
const logger = createBunyanLogger({
|
||||||
src: true,
|
src: true,
|
||||||
name: 'talk',
|
name: 'talk',
|
||||||
version,
|
version,
|
||||||
revision: REVISION_HASH,
|
revision: REVISION_HASH,
|
||||||
level: LOGGING_LEVEL,
|
level: LOGGING_LEVEL,
|
||||||
serializers: Logger.stdSerializers,
|
streams,
|
||||||
|
serializers: stdSerializers,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create the logging instance that all logger's are branched from.
|
/**
|
||||||
function createLogger(name, traceID) {
|
*
|
||||||
return logger.child({ origin: name, traceID });
|
* @param {String} origin the origin name used by the logger
|
||||||
}
|
* @param {String} traceID the id of the request being made
|
||||||
|
*/
|
||||||
|
const createLogger = (origin, traceID) => logger.child({ origin, traceID });
|
||||||
|
|
||||||
module.exports = { logger, createLogger };
|
module.exports = { logger, createLogger };
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const errors = require('../../errors');
|
const { ErrNotFound } = require('../../errors');
|
||||||
const get = require('lodash/get');
|
const get = require('lodash/get');
|
||||||
|
|
||||||
// Load in the phases to use.
|
// Load in the phases to use.
|
||||||
@@ -92,14 +92,14 @@ const fetchOptions = async (ctx, comment) => {
|
|||||||
const assetID = get(comment, 'asset_id', null);
|
const assetID = get(comment, 'asset_id', null);
|
||||||
if (assetID === null) {
|
if (assetID === null) {
|
||||||
// And leave now if this asset wasn't found.
|
// And leave now if this asset wasn't found.
|
||||||
throw errors.ErrNotFound;
|
throw new ErrNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the asset.
|
// Load the asset.
|
||||||
const asset = await Assets.getByID.load(assetID);
|
const asset = await Assets.getByID.load(assetID);
|
||||||
if (!asset) {
|
if (!asset) {
|
||||||
// And leave now if this asset wasn't found.
|
// And leave now if this asset wasn't found.
|
||||||
throw errors.ErrNotFound;
|
throw new ErrNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Combine the asset and the settings to get the asset settings.
|
// Combine the asset and the settings to get the asset settings.
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ module.exports = (
|
|||||||
) => {
|
) => {
|
||||||
// Check to see if the body is too short, if it is, then complain about it!
|
// Check to see if the body is too short, if it is, then complain about it!
|
||||||
if (comment.body.length < 2) {
|
if (comment.body.length < 2) {
|
||||||
throw ErrCommentTooShort;
|
throw new ErrCommentTooShort(comment.body.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reject if the comment is too long
|
// Reject if the comment is too long
|
||||||
|
|||||||
+33
-38
@@ -1,48 +1,40 @@
|
|||||||
const { MONGO_URL, WEBPACK, CREATE_MONGO_INDEXES } = require('../config');
|
const {
|
||||||
|
MONGO_URL,
|
||||||
|
WEBPACK,
|
||||||
|
CREATE_MONGO_INDEXES,
|
||||||
|
LOGGING_LEVEL,
|
||||||
|
} = require('../config');
|
||||||
|
const { logger } = require('./logging');
|
||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
const debug = require('debug')('talk:db');
|
|
||||||
const enabled = require('debug').enabled;
|
|
||||||
const queryDebugger = require('debug')('talk:db:query');
|
|
||||||
|
|
||||||
// Loading the formatter from Mongoose:
|
|
||||||
//
|
|
||||||
// https://github.com/Automattic/mongoose/blob/1a93d1f4d12e441e17ddf451e96fbc5f6e8f54b8/lib/drivers/node-mongodb-native/collection.js#L182
|
|
||||||
//
|
|
||||||
// so we can wrap parameters.
|
|
||||||
const formatter = require('mongoose').Collection.prototype.$format;
|
|
||||||
|
|
||||||
// Provide a newly wrapped debugQuery function which wraps the `debug` package.
|
// Provide a newly wrapped debugQuery function which wraps the `debug` package.
|
||||||
function debugQuery(name, i, ...args) {
|
function debugQuery(name, operation, ...args) {
|
||||||
let functionCall = ['db', name, i].join('.');
|
logger.debug(
|
||||||
let _args = [];
|
{
|
||||||
for (let j = args.length - 1; j >= 0; --j) {
|
query: `db.${name}.${operation}(${args
|
||||||
if (formatter(args[j]) || _args.length) {
|
.map(arg => JSON.stringify(arg))
|
||||||
_args.unshift(formatter(args[j]));
|
.join(', ')})`,
|
||||||
}
|
},
|
||||||
}
|
'mongodb query'
|
||||||
|
);
|
||||||
let params = `(${_args.join(', ')})`;
|
|
||||||
|
|
||||||
queryDebugger(functionCall + params);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use native promises
|
// Use native promises
|
||||||
mongoose.Promise = global.Promise;
|
mongoose.Promise = global.Promise;
|
||||||
|
|
||||||
// Check if debugging is enabled on the talk:db prefix.
|
// Check if verbose logging is enabled.
|
||||||
if (enabled('talk:db:query')) {
|
if (['debug', 'trace'].includes(LOGGING_LEVEL)) {
|
||||||
// Enable the mongoose debugger, here we wrap the similar print function
|
// Enable the mongoose debugger, here we wrap the similar print function
|
||||||
// provided by setting the debug parameter.
|
// provided by setting the debug parameter.
|
||||||
mongoose.set('debug', debugQuery);
|
mongoose.set('debug', debugQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (WEBPACK) {
|
if (WEBPACK) {
|
||||||
debug('Not connecting to mongodb during webpack build');
|
logger.debug('Not connecting to mongodb during webpack build');
|
||||||
|
|
||||||
// @wyattjoh: We didn't call connect, but because we include mongoose, it will hold the socket ready,
|
// @wyattjoh: We didn't call connect, but because we include mongoose, it will
|
||||||
// preventing node from exiting. Calling disconnect here just ensures that the application
|
// hold the socket ready, preventing node from exiting. Calling disconnect
|
||||||
// can quit correctly.
|
// here just ensures that the application can quit correctly.
|
||||||
mongoose.disconnect();
|
mongoose.disconnect();
|
||||||
} else {
|
} else {
|
||||||
// Connect to the Mongo instance.
|
// Connect to the Mongo instance.
|
||||||
@@ -54,7 +46,7 @@ if (WEBPACK) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
debug('connection established');
|
logger.debug('mongodb connection established');
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
@@ -66,10 +58,13 @@ module.exports = mongoose;
|
|||||||
|
|
||||||
// Here we include all the models that mongoose is used for, this ensures that
|
// Here we include all the models that mongoose is used for, this ensures that
|
||||||
// when we import mongoose that we also start up all the indexing operations
|
// when we import mongoose that we also start up all the indexing operations
|
||||||
// here.
|
// here. No point also in importing this if we're not actually doing any
|
||||||
require('../models/action');
|
// indexing now.
|
||||||
require('../models/asset');
|
if (CREATE_MONGO_INDEXES) {
|
||||||
require('../models/comment');
|
require('../models/action');
|
||||||
require('../models/setting');
|
require('../models/asset');
|
||||||
require('../models/user');
|
require('../models/comment');
|
||||||
require('./migration');
|
require('../models/setting');
|
||||||
|
require('../models/user');
|
||||||
|
require('./migration');
|
||||||
|
}
|
||||||
|
|||||||
+13
-8
@@ -6,7 +6,12 @@ const TokensService = require('./tokens');
|
|||||||
const fetch = require('node-fetch');
|
const fetch = require('node-fetch');
|
||||||
const FormData = require('form-data');
|
const FormData = require('form-data');
|
||||||
const LocalStrategy = require('passport-local').Strategy;
|
const LocalStrategy = require('passport-local').Strategy;
|
||||||
const errors = require('../errors');
|
const {
|
||||||
|
ErrLoginAttemptMaximumExceeded,
|
||||||
|
ErrNotAuthorized,
|
||||||
|
ErrAuthentication,
|
||||||
|
ErrNotVerified,
|
||||||
|
} = require('../errors');
|
||||||
const uuid = require('uuid');
|
const uuid = require('uuid');
|
||||||
const debug = require('debug')('talk:services:passport');
|
const debug = require('debug')('talk:services:passport');
|
||||||
const bowser = require('bowser');
|
const bowser = require('bowser');
|
||||||
@@ -75,7 +80,7 @@ const HandleGenerateCredentials = (req, res, next) => (err, user) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return next(errors.ErrNotAuthorized);
|
return next(new ErrNotAuthorized());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate the token to re-issue to the frontend.
|
// Generate the token to re-issue to the frontend.
|
||||||
@@ -117,7 +122,7 @@ const HandleAuthPopupCallback = (req, res, next) => (err, user) => {
|
|||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return res.render('auth-callback', {
|
return res.render('auth-callback', {
|
||||||
auth: { err: errors.ErrNotAuthorized, data: null },
|
auth: { err: new ErrNotAuthorized(), data: null },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +148,7 @@ async function ValidateUserLogin(loginProfile, user, done) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (user.disabled) {
|
if (user.disabled) {
|
||||||
return done(new errors.ErrAuthentication('Account disabled'));
|
return done(new ErrAuthentication('Account disabled'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user isn't a local user (i.e., a social user).
|
// If the user isn't a local user (i.e., a social user).
|
||||||
@@ -169,7 +174,7 @@ async function ValidateUserLogin(loginProfile, user, done) {
|
|||||||
// If the profile doesn't have a metadata field, or it does not have a
|
// If the profile doesn't have a metadata field, or it does not have a
|
||||||
// confirmed_at field, or that field is null, then send them back.
|
// confirmed_at field, or that field is null, then send them back.
|
||||||
if (_.get(profile, 'metadata.confirmed_at', null) === null) {
|
if (_.get(profile, 'metadata.confirmed_at', null) === null) {
|
||||||
return done(errors.ErrNotVerified);
|
return done(new ErrNotVerified());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,7 +214,7 @@ const checkGeneralTokenBlacklist = jwt =>
|
|||||||
.get(`jtir[${jwt.jti}]`)
|
.get(`jtir[${jwt.jti}]`)
|
||||||
.then(expiry => {
|
.then(expiry => {
|
||||||
if (expiry != null) {
|
if (expiry != null) {
|
||||||
throw new errors.ErrAuthentication('token was revoked');
|
throw new ErrAuthentication('token was revoked');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -392,7 +397,7 @@ const HandleFailedAttempt = async (email, userNeedsRecaptcha) => {
|
|||||||
await UsersService.recordLoginAttempt(email);
|
await UsersService.recordLoginAttempt(email);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (
|
if (
|
||||||
err === errors.ErrLoginAttemptMaximumExceeded &&
|
err instanceof ErrLoginAttemptMaximumExceeded &&
|
||||||
!userNeedsRecaptcha &&
|
!userNeedsRecaptcha &&
|
||||||
RECAPTCHA_ENABLED
|
RECAPTCHA_ENABLED
|
||||||
) {
|
) {
|
||||||
@@ -448,7 +453,7 @@ passport.use(
|
|||||||
try {
|
try {
|
||||||
await UsersService.checkLoginAttempts(email);
|
await UsersService.checkLoginAttempts(email);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err === errors.ErrLoginAttemptMaximumExceeded) {
|
if (err instanceof ErrLoginAttemptMaximumExceeded) {
|
||||||
// This says, we didn't have a recaptcha, yet we needed one.. Reject
|
// This says, we didn't have a recaptcha, yet we needed one.. Reject
|
||||||
// here.
|
// here.
|
||||||
|
|
||||||
|
|||||||
+13
-12
@@ -1,7 +1,5 @@
|
|||||||
const Redis = require('ioredis');
|
const Redis = require('ioredis');
|
||||||
const merge = require('lodash/merge');
|
const merge = require('lodash/merge');
|
||||||
const debug = require('debug')('talk:services:redis');
|
|
||||||
const enabled = require('debug').enabled('talk:services:redis');
|
|
||||||
const {
|
const {
|
||||||
REDIS_URL,
|
REDIS_URL,
|
||||||
REDIS_RECONNECTION_BACKOFF_FACTOR,
|
REDIS_RECONNECTION_BACKOFF_FACTOR,
|
||||||
@@ -9,29 +7,32 @@ const {
|
|||||||
REDIS_CLIENT_CONFIG,
|
REDIS_CLIENT_CONFIG,
|
||||||
REDIS_CLUSTER_MODE,
|
REDIS_CLUSTER_MODE,
|
||||||
REDIS_CLUSTER_CONFIGURATION,
|
REDIS_CLUSTER_CONFIGURATION,
|
||||||
|
LOGGING_LEVEL,
|
||||||
} = require('../config');
|
} = require('../config');
|
||||||
|
const { createLogger } = require('./logging');
|
||||||
|
const logger = createLogger('redis');
|
||||||
|
|
||||||
const attachMonitors = client => {
|
const attachMonitors = client => {
|
||||||
debug('client created');
|
logger.debug('client created');
|
||||||
|
|
||||||
// Debug events.
|
// Debug events.
|
||||||
if (enabled) {
|
if (['debug', 'trace'].includes(LOGGING_LEVEL)) {
|
||||||
client.on('connect', () => debug('client connected'));
|
client.on('connect', () => logger.info('client connected'));
|
||||||
client.on('ready', () => debug('client ready'));
|
client.on('ready', () => logger.debug('client ready'));
|
||||||
client.on('close', () => debug('client closed the connection'));
|
client.on('close', () => logger.debug('client closed the connection'));
|
||||||
client.on('reconnecting', () =>
|
client.on('reconnecting', () =>
|
||||||
debug('client connection lost, attempting to reconnect')
|
logger.debug('client connection lost, attempting to reconnect')
|
||||||
);
|
);
|
||||||
client.on('end', () => debug('client ended'));
|
client.on('end', () => logger.debug('client ended'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error events.
|
// Error events.
|
||||||
client.on('error', err => {
|
client.on('error', err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error('Error connecting to redis:', err);
|
logger.error({ err }, 'cannot connect to redis');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
client.on('node error', err => debug('node error', err));
|
client.on('node error', err => logger.error({ err }, 'node error'));
|
||||||
};
|
};
|
||||||
|
|
||||||
function retryStrategy(times) {
|
function retryStrategy(times) {
|
||||||
@@ -40,7 +41,7 @@ function retryStrategy(times) {
|
|||||||
REDIS_RECONNECTION_BACKOFF_MINIMUM_TIME
|
REDIS_RECONNECTION_BACKOFF_MINIMUM_TIME
|
||||||
);
|
);
|
||||||
|
|
||||||
debug(`retry strategy: try to reconnect ${delay} ms from now`);
|
logger.debug(`retry strategy: try to reconnect ${delay} ms from now`);
|
||||||
|
|
||||||
return delay;
|
return delay;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const SettingModel = require('../models/setting');
|
const SettingModel = require('../models/setting');
|
||||||
const cache = require('./cache');
|
const cache = require('./cache');
|
||||||
const errors = require('../errors');
|
const { ErrSettingsNotInit } = require('../errors');
|
||||||
const { dotize } = require('./utils');
|
const { dotize } = require('./utils');
|
||||||
const { SETTINGS_CACHE_TIME } = require('../config');
|
const { SETTINGS_CACHE_TIME } = require('../config');
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ const retrieve = async fields => {
|
|||||||
settings = await SettingModel.findOne(selector);
|
settings = await SettingModel.findOne(selector);
|
||||||
}
|
}
|
||||||
if (!settings) {
|
if (!settings) {
|
||||||
throw errors.ErrSettingsNotInit;
|
throw new ErrSettingsNotInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
return settings;
|
return settings;
|
||||||
|
|||||||
+17
-12
@@ -2,7 +2,12 @@ const UsersService = require('./users');
|
|||||||
const SettingsService = require('./settings');
|
const SettingsService = require('./settings');
|
||||||
const MigrationService = require('./migration');
|
const MigrationService = require('./migration');
|
||||||
const SettingsModel = require('../models/setting');
|
const SettingsModel = require('../models/setting');
|
||||||
const errors = require('../errors');
|
const {
|
||||||
|
ErrMissingEmail,
|
||||||
|
ErrInstallLock,
|
||||||
|
ErrSettingsInit,
|
||||||
|
ErrSettingsNotInit,
|
||||||
|
} = require('../errors');
|
||||||
const { INSTALL_LOCK } = require('../config');
|
const { INSTALL_LOCK } = require('../config');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -16,25 +21,25 @@ module.exports = class SetupService {
|
|||||||
static async isAvailable() {
|
static async isAvailable() {
|
||||||
// Check if we have an install lock present.
|
// Check if we have an install lock present.
|
||||||
if (INSTALL_LOCK) {
|
if (INSTALL_LOCK) {
|
||||||
throw errors.ErrInstallLock;
|
throw new ErrInstallLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get the current settings, we are expecing an error here.
|
// Get the current settings, we are expecting an error here.
|
||||||
await SettingsService.retrieve();
|
await SettingsService.retrieve();
|
||||||
|
|
||||||
// We should NOT have gotten a settings object, this means that the
|
// We should NOT have gotten a settings object, this means that the
|
||||||
// application is already setup. Error out here.
|
// application is already setup. Error out here.
|
||||||
throw errors.ErrSettingsInit;
|
throw new ErrSettingsInit();
|
||||||
} catch (e) {
|
} catch (err) {
|
||||||
// If the error is `not init`, then we're good, otherwise, it's something
|
// Allow the request to keep going here.
|
||||||
// else.
|
if (err instanceof ErrSettingsNotInit) {
|
||||||
if (e !== errors.ErrSettingsNotInit) {
|
return;
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow the request to keep going here.
|
// If the error is `not init`, then we're good, otherwise, it's something
|
||||||
return;
|
// else.
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +49,7 @@ module.exports = class SetupService {
|
|||||||
static validate({ settings, user: { email, username, password } }) {
|
static validate({ settings, user: { email, username, password } }) {
|
||||||
// Verify the email address of the user.
|
// Verify the email address of the user.
|
||||||
if (!email) {
|
if (!email) {
|
||||||
return Promise.reject(errors.ErrMissingEmail);
|
throw new ErrMissingEmail();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a settings model to use for validation.
|
// Create a settings model to use for validation.
|
||||||
|
|||||||
+3
-5
@@ -1,12 +1,10 @@
|
|||||||
const CommentModel = require('../models/comment');
|
const CommentModel = require('../models/comment');
|
||||||
const AssetModel = require('../models/asset');
|
const AssetModel = require('../models/asset');
|
||||||
const UserModel = require('../models/user');
|
const UserModel = require('../models/user');
|
||||||
|
|
||||||
const AssetsService = require('./assets');
|
const AssetsService = require('./assets');
|
||||||
const SettingsService = require('./settings');
|
const SettingsService = require('./settings');
|
||||||
const { ADD_COMMENT_TAG } = require('../perms/constants');
|
const { ADD_COMMENT_TAG } = require('../perms/constants');
|
||||||
|
const { ErrNotAuthorized } = require('../errors');
|
||||||
const errors = require('../errors');
|
|
||||||
|
|
||||||
const updateModel = async (item_type, query, update) => {
|
const updateModel = async (item_type, query, update) => {
|
||||||
// Get the model to update with.
|
// Get the model to update with.
|
||||||
@@ -120,13 +118,13 @@ class TagsService {
|
|||||||
return { tagLink, ownership: true };
|
return { tagLink, ownership: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
throw errors.ErrNotAuthorized;
|
throw new ErrNotAuthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only admin/moderators can modify unique tags, these are tags that are not
|
// Only admin/moderators can modify unique tags, these are tags that are not
|
||||||
// in the global list.
|
// in the global list.
|
||||||
if (!user.can(ADD_COMMENT_TAG)) {
|
if (!user.can(ADD_COMMENT_TAG)) {
|
||||||
throw errors.ErrNotAuthorized;
|
throw new ErrNotAuthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate the tag in the event now that we have to create the tag for this
|
// Generate the tag in the event now that we have to create the tag for this
|
||||||
|
|||||||
+41
-28
@@ -1,6 +1,21 @@
|
|||||||
const uuid = require('uuid');
|
const uuid = require('uuid');
|
||||||
const bcrypt = require('bcryptjs');
|
const bcrypt = require('bcryptjs');
|
||||||
const errors = require('../errors');
|
const {
|
||||||
|
ErrMaxRateLimit,
|
||||||
|
ErrLoginAttemptMaximumExceeded,
|
||||||
|
ErrNotFound,
|
||||||
|
ErrPermissionUpdateUsername,
|
||||||
|
ErrSameUsernameProvided,
|
||||||
|
ErrUsernameTaken,
|
||||||
|
ErrMissingUsername,
|
||||||
|
ErrSpecialChars,
|
||||||
|
ErrMissingPassword,
|
||||||
|
ErrPasswordTooShort,
|
||||||
|
ErrMissingEmail,
|
||||||
|
ErrEmailTaken,
|
||||||
|
ErrEmailAlreadyVerified,
|
||||||
|
ErrCannotIgnoreStaff,
|
||||||
|
} = require('../errors');
|
||||||
const { difference, sample, some, merge, random } = require('lodash');
|
const { difference, sample, some, merge, random } = require('lodash');
|
||||||
const { ROOT_URL } = require('../config');
|
const { ROOT_URL } = require('../config');
|
||||||
const { jwt: JWT_SECRET } = require('../secrets');
|
const { jwt: JWT_SECRET } = require('../secrets');
|
||||||
@@ -59,8 +74,8 @@ class UsersService {
|
|||||||
try {
|
try {
|
||||||
await loginRateLimiter.test(email.toLowerCase().trim());
|
await loginRateLimiter.test(email.toLowerCase().trim());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err === errors.ErrMaxRateLimit) {
|
if (err instanceof ErrMaxRateLimit) {
|
||||||
throw errors.ErrLoginAttemptMaximumExceeded;
|
throw new ErrLoginAttemptMaximumExceeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
throw err;
|
throw err;
|
||||||
@@ -91,7 +106,7 @@ class UsersService {
|
|||||||
if (user === null) {
|
if (user === null) {
|
||||||
user = await UserModel.findOne({ id });
|
user = await UserModel.findOne({ id });
|
||||||
if (user === null) {
|
if (user === null) {
|
||||||
throw errors.ErrNotFound;
|
throw new ErrNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Date comparisons are difficult when using MongoDB. Javascript will
|
// Date comparisons are difficult when using MongoDB. Javascript will
|
||||||
@@ -150,10 +165,10 @@ class UsersService {
|
|||||||
runValidators: true,
|
runValidators: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
if (user === null) {
|
if (!user) {
|
||||||
user = await UserModel.findOne({ id });
|
user = await UserModel.findOne({ id });
|
||||||
if (user === null) {
|
if (!user) {
|
||||||
throw errors.ErrNotFound;
|
throw new ErrNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.status.banned.status === status) {
|
if (user.status.banned.status === status) {
|
||||||
@@ -204,7 +219,7 @@ class UsersService {
|
|||||||
if (user === null) {
|
if (user === null) {
|
||||||
user = await UserModel.findOne({ id });
|
user = await UserModel.findOne({ id });
|
||||||
if (user === null) {
|
if (user === null) {
|
||||||
throw errors.ErrNotFound;
|
throw new ErrNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.status.username.status === status) {
|
if (user.status.username.status === status) {
|
||||||
@@ -259,15 +274,15 @@ class UsersService {
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
user = await UsersService.findById(id);
|
user = await UsersService.findById(id);
|
||||||
if (user === null) {
|
if (user === null) {
|
||||||
throw errors.ErrNotFound;
|
throw new ErrNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.status.username.status !== fromStatus) {
|
if (user.status.username.status !== fromStatus) {
|
||||||
throw errors.ErrPermissionUpdateUsername;
|
throw new ErrPermissionUpdateUsername();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!resetAllowed && user.username === username) {
|
if (!resetAllowed && user.username === username) {
|
||||||
throw errors.ErrSameUsernameProvided;
|
throw new ErrSameUsernameProvided();
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('edit username failed for an unexpected reason');
|
throw new Error('edit username failed for an unexpected reason');
|
||||||
@@ -276,7 +291,7 @@ class UsersService {
|
|||||||
return user;
|
return user;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code === 11000) {
|
if (err.code === 11000) {
|
||||||
throw errors.ErrUsernameTaken;
|
throw new ErrUsernameTaken();
|
||||||
}
|
}
|
||||||
|
|
||||||
throw err;
|
throw err;
|
||||||
@@ -317,7 +332,7 @@ class UsersService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (attempts >= RECAPTCHA_INCORRECT_TRIGGER) {
|
if (attempts >= RECAPTCHA_INCORRECT_TRIGGER) {
|
||||||
throw errors.ErrLoginAttemptMaximumExceeded;
|
throw new ErrLoginAttemptMaximumExceeded();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -515,11 +530,11 @@ class UsersService {
|
|||||||
const onlyLettersNumbersUnderscore = /^[A-Za-z0-9_]+$/;
|
const onlyLettersNumbersUnderscore = /^[A-Za-z0-9_]+$/;
|
||||||
|
|
||||||
if (!username) {
|
if (!username) {
|
||||||
throw errors.ErrMissingUsername;
|
throw new ErrMissingUsername();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!onlyLettersNumbersUnderscore.test(username)) {
|
if (!onlyLettersNumbersUnderscore.test(username)) {
|
||||||
throw errors.ErrSpecialChars;
|
throw new ErrSpecialChars();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (checkAgainstWordlist) {
|
if (checkAgainstWordlist) {
|
||||||
@@ -539,11 +554,11 @@ class UsersService {
|
|||||||
*/
|
*/
|
||||||
static isValidPassword(password) {
|
static isValidPassword(password) {
|
||||||
if (!password) {
|
if (!password) {
|
||||||
throw errors.ErrMissingPassword;
|
throw new ErrMissingPassword();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (password.length < 8) {
|
if (password.length < 8) {
|
||||||
throw errors.ErrPasswordTooShort;
|
throw new ErrPasswordTooShort();
|
||||||
}
|
}
|
||||||
|
|
||||||
return password;
|
return password;
|
||||||
@@ -558,7 +573,7 @@ class UsersService {
|
|||||||
*/
|
*/
|
||||||
static async createLocalUser(ctx, email, password, username) {
|
static async createLocalUser(ctx, email, password, username) {
|
||||||
if (!email) {
|
if (!email) {
|
||||||
throw errors.ErrMissingEmail;
|
throw new ErrMissingEmail();
|
||||||
}
|
}
|
||||||
|
|
||||||
email = email.toLowerCase().trim();
|
email = email.toLowerCase().trim();
|
||||||
@@ -596,9 +611,9 @@ class UsersService {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code === 11000) {
|
if (err.code === 11000) {
|
||||||
if (err.message.match('Username')) {
|
if (err.message.match('Username')) {
|
||||||
throw errors.ErrUsernameTaken;
|
throw new ErrUsernameTaken();
|
||||||
}
|
}
|
||||||
throw errors.ErrEmailTaken;
|
throw new ErrEmailTaken();
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
@@ -678,9 +693,7 @@ class UsersService {
|
|||||||
*/
|
*/
|
||||||
static async createPasswordResetToken(email, loc) {
|
static async createPasswordResetToken(email, loc) {
|
||||||
if (!email || typeof email !== 'string') {
|
if (!email || typeof email !== 'string') {
|
||||||
throw new Error(
|
throw new ErrMissingEmail();
|
||||||
'email is required when creating a JWT for resetting passord'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
email = email.toLowerCase();
|
email = email.toLowerCase();
|
||||||
@@ -837,7 +850,7 @@ class UsersService {
|
|||||||
|
|
||||||
// Ensure that the user email hasn't already been verified.
|
// Ensure that the user email hasn't already been verified.
|
||||||
if (profile && profile.metadata && profile.metadata.confirmed_at) {
|
if (profile && profile.metadata && profile.metadata.confirmed_at) {
|
||||||
throw errors.ErrEmailAlreadyVerified;
|
throw new ErrEmailAlreadyVerified();
|
||||||
}
|
}
|
||||||
|
|
||||||
return JWT_SECRET.sign(
|
return JWT_SECRET.sign(
|
||||||
@@ -875,16 +888,16 @@ class UsersService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw errors.ErrNotFound;
|
throw new ErrNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
const profile = user.profiles.find(({ id }) => id === decoded.email);
|
const profile = user.profiles.find(({ id }) => id === decoded.email);
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
throw errors.ErrNotFound;
|
throw new ErrNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (profile.metadata && profile.metadata.confirmed_at !== null) {
|
if (profile.metadata && profile.metadata.confirmed_at !== null) {
|
||||||
throw errors.ErrEmailAlreadyVerified;
|
throw new ErrEmailAlreadyVerified();
|
||||||
}
|
}
|
||||||
|
|
||||||
return decoded;
|
return decoded;
|
||||||
@@ -943,7 +956,7 @@ class UsersService {
|
|||||||
|
|
||||||
const users = await UsersService.findByIdArray(usersToIgnore);
|
const users = await UsersService.findByIdArray(usersToIgnore);
|
||||||
if (some(users, user => user.isStaff())) {
|
if (some(users, user => user.isStaff())) {
|
||||||
throw errors.ErrCannotIgnoreStaff;
|
throw new ErrCannotIgnoreStaff();
|
||||||
}
|
}
|
||||||
|
|
||||||
return UserModel.update(
|
return UserModel.update(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const debug = require('debug')('talk:services:wordlist');
|
const debug = require('debug')('talk:services:wordlist');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const SettingsService = require('./settings');
|
const SettingsService = require('./settings');
|
||||||
const Errors = require('../errors');
|
const { ErrContainsProfanity } = require('../errors');
|
||||||
const memoize = require('lodash/memoize');
|
const memoize = require('lodash/memoize');
|
||||||
const { escapeRegExp } = require('./regex');
|
const { escapeRegExp } = require('./regex');
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@ class Wordlist {
|
|||||||
`the field "${fieldName}" contained a phrase "${phrase}" which contained a banned word/phrase`
|
`the field "${fieldName}" contained a phrase "${phrase}" which contained a banned word/phrase`
|
||||||
);
|
);
|
||||||
|
|
||||||
errors.banned = Errors.ErrContainsProfanity;
|
errors.banned = new ErrContainsProfanity(phrase);
|
||||||
|
|
||||||
// Stop looping through the fields now, we discovered the worst possible
|
// Stop looping through the fields now, we discovered the worst possible
|
||||||
// situation (a banned word).
|
// situation (a banned word).
|
||||||
@@ -109,7 +109,7 @@ class Wordlist {
|
|||||||
`the field "${fieldName}" contained a phrase "${phrase}" which contained a suspected word/phrase`
|
`the field "${fieldName}" contained a phrase "${phrase}" which contained a suspected word/phrase`
|
||||||
);
|
);
|
||||||
|
|
||||||
errors.suspect = Errors.ErrContainsProfanity;
|
errors.suspect = new ErrContainsProfanity(phrase);
|
||||||
|
|
||||||
// Continue looping through the fields now, we discovered a possible bad
|
// Continue looping through the fields now, we discovered a possible bad
|
||||||
// word (suspect).
|
// word (suspect).
|
||||||
@@ -167,7 +167,7 @@ class Wordlist {
|
|||||||
|
|
||||||
return wl.load().then(() => {
|
return wl.load().then(() => {
|
||||||
if (wl.regexp.banned.test(username)) {
|
if (wl.regexp.banned.test(username)) {
|
||||||
return Errors.ErrContainsProfanity;
|
throw new ErrContainsProfanity(username);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const User = require('../../../models/user');
|
const User = require('../../../models/user');
|
||||||
const Context = require('../../../graph/context');
|
const Context = require('../../../graph/context');
|
||||||
const errors = require('../../../errors');
|
const { ErrNotAuthorized } = require('../../../errors');
|
||||||
const SettingsService = require('../../../services/settings');
|
const SettingsService = require('../../../services/settings');
|
||||||
|
|
||||||
const { expect } = require('chai');
|
const { expect } = require('chai');
|
||||||
@@ -54,7 +54,7 @@ describe('graph.Context', () => {
|
|||||||
throw new Error('should not reach this point');
|
throw new Error('should not reach this point');
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
expect(err).to.be.equal(errors.ErrNotAuthorized);
|
expect(err).to.be.an.instanceof(ErrNotAuthorized);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const Errors = require('../../../errors');
|
const { ErrContainsProfanity } = require('../../../errors');
|
||||||
const Wordlist = require('../../../services/wordlist');
|
const Wordlist = require('../../../services/wordlist');
|
||||||
const SettingsService = require('../../../services/settings');
|
const SettingsService = require('../../../services/settings');
|
||||||
|
|
||||||
@@ -103,7 +103,8 @@ describe('services.Wordlist', () => {
|
|||||||
'content'
|
'content'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(errors).to.have.property('banned', Errors.ErrContainsProfanity);
|
expect(errors).to.have.property('banned');
|
||||||
|
expect(errors.banned).to.be.an.instanceof(ErrContainsProfanity);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not match on bodies not containing bad words', () => {
|
it('does not match on bodies not containing bad words', () => {
|
||||||
|
|||||||
@@ -68,7 +68,7 @@
|
|||||||
lodash "^4.2.0"
|
lodash "^4.2.0"
|
||||||
to-fast-properties "^2.0.0"
|
to-fast-properties "^2.0.0"
|
||||||
|
|
||||||
"@coralproject/eslint-config-talk@^0.1.0":
|
"@coralproject/eslint-config-talk@^0.1.0", "@coralproject/eslint-config-talk@^0.1.1":
|
||||||
version "0.1.1"
|
version "0.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/@coralproject/eslint-config-talk/-/eslint-config-talk-0.1.1.tgz#71991b4937a3ffe657128d7f1170da4b5fb75c9e"
|
resolved "https://registry.yarnpkg.com/@coralproject/eslint-config-talk/-/eslint-config-talk-0.1.1.tgz#71991b4937a3ffe657128d7f1170da4b5fb75c9e"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -126,6 +126,12 @@
|
|||||||
to-title-case "~1.0.0"
|
to-title-case "~1.0.0"
|
||||||
url-regex "~4.1.1"
|
url-regex "~4.1.1"
|
||||||
|
|
||||||
|
"@types/form-data@*":
|
||||||
|
version "2.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-2.2.1.tgz#ee2b3b8eaa11c0938289953606b745b738c54b1e"
|
||||||
|
dependencies:
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/graphql@0.10.2":
|
"@types/graphql@0.10.2":
|
||||||
version "0.10.2"
|
version "0.10.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.10.2.tgz#d7c79acbaa17453b6681c80c34b38fcb10c4c08c"
|
resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.10.2.tgz#d7c79acbaa17453b6681c80c34b38fcb10c4c08c"
|
||||||
@@ -138,10 +144,25 @@
|
|||||||
version "0.9.4"
|
version "0.9.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.9.4.tgz#cdeb6bcbef9b6c584374b81aa7f48ecf3da404fa"
|
resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.9.4.tgz#cdeb6bcbef9b6c584374b81aa7f48ecf3da404fa"
|
||||||
|
|
||||||
|
"@types/lodash@^4.14.50":
|
||||||
|
version "4.14.106"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.106.tgz#6093e9a02aa567ddecfe9afadca89e53e5dce4dd"
|
||||||
|
|
||||||
"@types/node@*":
|
"@types/node@*":
|
||||||
version "8.0.53"
|
version "8.0.53"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.53.tgz#396b35af826fa66aad472c8cb7b8d5e277f4e6d8"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.53.tgz#396b35af826fa66aad472c8cb7b8d5e277f4e6d8"
|
||||||
|
|
||||||
|
"@types/node@^7.0.0":
|
||||||
|
version "7.0.59"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.59.tgz#fd7dceba9521c2d62c3e0eda8c5d704bf88b261d"
|
||||||
|
|
||||||
|
"@types/request@^0.0.39":
|
||||||
|
version "0.0.39"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/request/-/request-0.0.39.tgz#168b96cf4253c5d54d403f746f82ee7aed47ce2c"
|
||||||
|
dependencies:
|
||||||
|
"@types/form-data" "*"
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/ws@^3.0.0":
|
"@types/ws@^3.0.0":
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-3.2.0.tgz#988ff690e6ed10068a86aa0e9f842d0a03c09e21"
|
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-3.2.0.tgz#988ff690e6ed10068a86aa0e9f842d0a03c09e21"
|
||||||
@@ -174,6 +195,13 @@ accepts@^1.3.4, accepts@~1.3.4:
|
|||||||
mime-types "~2.1.16"
|
mime-types "~2.1.16"
|
||||||
negotiator "0.6.1"
|
negotiator "0.6.1"
|
||||||
|
|
||||||
|
accepts@~1.3.5:
|
||||||
|
version "1.3.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2"
|
||||||
|
dependencies:
|
||||||
|
mime-types "~2.1.18"
|
||||||
|
negotiator "0.6.1"
|
||||||
|
|
||||||
acorn-dynamic-import@^2.0.0:
|
acorn-dynamic-import@^2.0.0:
|
||||||
version "2.0.2"
|
version "2.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz#c752bd210bef679501b6c6cb7fc84f8f47158cc4"
|
resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz#c752bd210bef679501b6c6cb7fc84f8f47158cc4"
|
||||||
@@ -228,6 +256,10 @@ acorn@^5.3.0:
|
|||||||
version "5.4.1"
|
version "5.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.4.1.tgz#fdc58d9d17f4a4e98d102ded826a9b9759125102"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.4.1.tgz#fdc58d9d17f4a4e98d102ded826a9b9759125102"
|
||||||
|
|
||||||
|
acorn@^5.5.0:
|
||||||
|
version "5.5.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.3.tgz#f473dd47e0277a08e28e9bec5aeeb04751f0b8c9"
|
||||||
|
|
||||||
addressparser@1.0.1:
|
addressparser@1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/addressparser/-/addressparser-1.0.1.tgz#47afbe1a2a9262191db6838e4fd1d39b40821746"
|
resolved "https://registry.yarnpkg.com/addressparser/-/addressparser-1.0.1.tgz#47afbe1a2a9262191db6838e4fd1d39b40821746"
|
||||||
@@ -1652,6 +1684,13 @@ builtin-status-codes@^3.0.0:
|
|||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
|
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
|
||||||
|
|
||||||
|
bunyan-debug-stream@^1.0.8:
|
||||||
|
version "1.0.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/bunyan-debug-stream/-/bunyan-debug-stream-1.0.8.tgz#df612852d5d0b6d6df3f30214d8a7e4ee925106d"
|
||||||
|
dependencies:
|
||||||
|
colors "^1.0.3"
|
||||||
|
exception-formatter "^1.0.4"
|
||||||
|
|
||||||
bunyan@^1.8.12:
|
bunyan@^1.8.12:
|
||||||
version "1.8.12"
|
version "1.8.12"
|
||||||
resolved "https://registry.yarnpkg.com/bunyan/-/bunyan-1.8.12.tgz#f150f0f6748abdd72aeae84f04403be2ef113797"
|
resolved "https://registry.yarnpkg.com/bunyan/-/bunyan-1.8.12.tgz#f150f0f6748abdd72aeae84f04403be2ef113797"
|
||||||
@@ -1770,6 +1809,13 @@ caseless@~0.12.0:
|
|||||||
version "0.12.0"
|
version "0.12.0"
|
||||||
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
||||||
|
|
||||||
|
casual@^1.5.19:
|
||||||
|
version "1.5.19"
|
||||||
|
resolved "https://registry.yarnpkg.com/casual/-/casual-1.5.19.tgz#66fac46f7ae463f468f5913eb139f9c41c58bbf2"
|
||||||
|
dependencies:
|
||||||
|
mersenne-twister "^1.0.1"
|
||||||
|
moment "^2.15.2"
|
||||||
|
|
||||||
center-align@^0.1.1:
|
center-align@^0.1.1:
|
||||||
version "0.1.3"
|
version "0.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad"
|
resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad"
|
||||||
@@ -2135,6 +2181,10 @@ colors@1.0.3, colors@1.0.x:
|
|||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
|
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
|
||||||
|
|
||||||
|
colors@^1.0.3:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.1.tgz#f4a3d302976aaf042356ba1ade3b1a2c62d9d794"
|
||||||
|
|
||||||
colors@^1.1.2, colors@~1.1.2:
|
colors@^1.1.2, colors@~1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
|
resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
|
||||||
@@ -2427,6 +2477,10 @@ cosmiconfig@^4.0.0, cosmiconfig@~4.0.0:
|
|||||||
parse-json "^4.0.0"
|
parse-json "^4.0.0"
|
||||||
require-from-string "^2.0.1"
|
require-from-string "^2.0.1"
|
||||||
|
|
||||||
|
crc@3.4.4:
|
||||||
|
version "3.4.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/crc/-/crc-3.4.4.tgz#9da1e980e3bd44fc5c93bf5ab3da3378d85e466b"
|
||||||
|
|
||||||
create-ecdh@^4.0.0:
|
create-ecdh@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.0.tgz#888c723596cdf7612f6498233eebd7a35301737d"
|
resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.0.tgz#888c723596cdf7612f6498233eebd7a35301737d"
|
||||||
@@ -2916,7 +2970,7 @@ dns-prefetch-control@0.1.0:
|
|||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/dns-prefetch-control/-/dns-prefetch-control-0.1.0.tgz#60ddb457774e178f1f9415f0cabb0e85b0b300b2"
|
resolved "https://registry.yarnpkg.com/dns-prefetch-control/-/dns-prefetch-control-0.1.0.tgz#60ddb457774e178f1f9415f0cabb0e85b0b300b2"
|
||||||
|
|
||||||
doctrine@^2.0.0:
|
doctrine@^2.0.0, doctrine@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
|
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -3057,6 +3111,10 @@ ejs@2.5.7, ejs@^2.5.7:
|
|||||||
version "2.5.7"
|
version "2.5.7"
|
||||||
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a"
|
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a"
|
||||||
|
|
||||||
|
ejs@^2.5.8:
|
||||||
|
version "2.5.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.8.tgz#2ab6954619f225e6193b7ac5f7c39c48fefe4380"
|
||||||
|
|
||||||
electron-to-chromium@^1.2.7:
|
electron-to-chromium@^1.2.7:
|
||||||
version "1.3.26"
|
version "1.3.26"
|
||||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.26.tgz#996427294861a74d9c7c82b9260ea301e8c02d66"
|
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.26.tgz#996427294861a74d9c7c82b9260ea301e8c02d66"
|
||||||
@@ -3352,6 +3410,49 @@ eslint-visitor-keys@^1.0.0:
|
|||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
|
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
|
||||||
|
|
||||||
|
eslint@^4.19.1:
|
||||||
|
version "4.19.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.19.1.tgz#32d1d653e1d90408854bfb296f076ec7e186a300"
|
||||||
|
dependencies:
|
||||||
|
ajv "^5.3.0"
|
||||||
|
babel-code-frame "^6.22.0"
|
||||||
|
chalk "^2.1.0"
|
||||||
|
concat-stream "^1.6.0"
|
||||||
|
cross-spawn "^5.1.0"
|
||||||
|
debug "^3.1.0"
|
||||||
|
doctrine "^2.1.0"
|
||||||
|
eslint-scope "^3.7.1"
|
||||||
|
eslint-visitor-keys "^1.0.0"
|
||||||
|
espree "^3.5.4"
|
||||||
|
esquery "^1.0.0"
|
||||||
|
esutils "^2.0.2"
|
||||||
|
file-entry-cache "^2.0.0"
|
||||||
|
functional-red-black-tree "^1.0.1"
|
||||||
|
glob "^7.1.2"
|
||||||
|
globals "^11.0.1"
|
||||||
|
ignore "^3.3.3"
|
||||||
|
imurmurhash "^0.1.4"
|
||||||
|
inquirer "^3.0.6"
|
||||||
|
is-resolvable "^1.0.0"
|
||||||
|
js-yaml "^3.9.1"
|
||||||
|
json-stable-stringify-without-jsonify "^1.0.1"
|
||||||
|
levn "^0.3.0"
|
||||||
|
lodash "^4.17.4"
|
||||||
|
minimatch "^3.0.2"
|
||||||
|
mkdirp "^0.5.1"
|
||||||
|
natural-compare "^1.4.0"
|
||||||
|
optionator "^0.8.2"
|
||||||
|
path-is-inside "^1.0.2"
|
||||||
|
pluralize "^7.0.0"
|
||||||
|
progress "^2.0.0"
|
||||||
|
regexpp "^1.0.1"
|
||||||
|
require-uncached "^1.0.3"
|
||||||
|
semver "^5.3.0"
|
||||||
|
strip-ansi "^4.0.0"
|
||||||
|
strip-json-comments "~2.0.1"
|
||||||
|
table "4.0.2"
|
||||||
|
text-table "~0.2.0"
|
||||||
|
|
||||||
eslint@^4.5.0:
|
eslint@^4.5.0:
|
||||||
version "4.13.1"
|
version "4.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.13.1.tgz#0055e0014464c7eb7878caf549ef2941992b444f"
|
resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.13.1.tgz#0055e0014464c7eb7878caf549ef2941992b444f"
|
||||||
@@ -3401,6 +3502,13 @@ espree@^3.5.2:
|
|||||||
acorn "^5.2.1"
|
acorn "^5.2.1"
|
||||||
acorn-jsx "^3.0.0"
|
acorn-jsx "^3.0.0"
|
||||||
|
|
||||||
|
espree@^3.5.4:
|
||||||
|
version "3.5.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7"
|
||||||
|
dependencies:
|
||||||
|
acorn "^5.5.0"
|
||||||
|
acorn-jsx "^3.0.0"
|
||||||
|
|
||||||
esprima@3.x.x, esprima@^3.1.3:
|
esprima@3.x.x, esprima@^3.1.3:
|
||||||
version "3.1.3"
|
version "3.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
|
resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
|
||||||
@@ -3480,6 +3588,12 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
|
|||||||
md5.js "^1.3.4"
|
md5.js "^1.3.4"
|
||||||
safe-buffer "^5.1.1"
|
safe-buffer "^5.1.1"
|
||||||
|
|
||||||
|
exception-formatter@^1.0.4:
|
||||||
|
version "1.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/exception-formatter/-/exception-formatter-1.0.5.tgz#bda957319789cbabdf36848fb5288c59634b73a5"
|
||||||
|
dependencies:
|
||||||
|
colors "^1.0.3"
|
||||||
|
|
||||||
exec-sh@^0.2.0:
|
exec-sh@^0.2.0:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.1.tgz#163b98a6e89e6b65b47c2a28d215bc1f63989c38"
|
resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.1.tgz#163b98a6e89e6b65b47c2a28d215bc1f63989c38"
|
||||||
@@ -3575,6 +3689,20 @@ exports-loader@^0.6.4:
|
|||||||
loader-utils "^1.0.2"
|
loader-utils "^1.0.2"
|
||||||
source-map "0.5.x"
|
source-map "0.5.x"
|
||||||
|
|
||||||
|
express-session@^1.15.6:
|
||||||
|
version "1.15.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.15.6.tgz#47b4160c88f42ab70fe8a508e31cbff76757ab0a"
|
||||||
|
dependencies:
|
||||||
|
cookie "0.3.1"
|
||||||
|
cookie-signature "1.0.6"
|
||||||
|
crc "3.4.4"
|
||||||
|
debug "2.6.9"
|
||||||
|
depd "~1.1.1"
|
||||||
|
on-headers "~1.0.1"
|
||||||
|
parseurl "~1.3.2"
|
||||||
|
uid-safe "~2.1.5"
|
||||||
|
utils-merge "1.0.1"
|
||||||
|
|
||||||
express-static-gzip@^0.3.1:
|
express-static-gzip@^0.3.1:
|
||||||
version "0.3.2"
|
version "0.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/express-static-gzip/-/express-static-gzip-0.3.2.tgz#89ede84547a5717de3146315f62dc996c071a88d"
|
resolved "https://registry.yarnpkg.com/express-static-gzip/-/express-static-gzip-0.3.2.tgz#89ede84547a5717de3146315f62dc996c071a88d"
|
||||||
@@ -3616,6 +3744,41 @@ express@4.16.0, express@^4.12.2:
|
|||||||
utils-merge "1.0.1"
|
utils-merge "1.0.1"
|
||||||
vary "~1.1.2"
|
vary "~1.1.2"
|
||||||
|
|
||||||
|
express@^4.16.3:
|
||||||
|
version "4.16.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/express/-/express-4.16.3.tgz#6af8a502350db3246ecc4becf6b5a34d22f7ed53"
|
||||||
|
dependencies:
|
||||||
|
accepts "~1.3.5"
|
||||||
|
array-flatten "1.1.1"
|
||||||
|
body-parser "1.18.2"
|
||||||
|
content-disposition "0.5.2"
|
||||||
|
content-type "~1.0.4"
|
||||||
|
cookie "0.3.1"
|
||||||
|
cookie-signature "1.0.6"
|
||||||
|
debug "2.6.9"
|
||||||
|
depd "~1.1.2"
|
||||||
|
encodeurl "~1.0.2"
|
||||||
|
escape-html "~1.0.3"
|
||||||
|
etag "~1.8.1"
|
||||||
|
finalhandler "1.1.1"
|
||||||
|
fresh "0.5.2"
|
||||||
|
merge-descriptors "1.0.1"
|
||||||
|
methods "~1.1.2"
|
||||||
|
on-finished "~2.3.0"
|
||||||
|
parseurl "~1.3.2"
|
||||||
|
path-to-regexp "0.1.7"
|
||||||
|
proxy-addr "~2.0.3"
|
||||||
|
qs "6.5.1"
|
||||||
|
range-parser "~1.2.0"
|
||||||
|
safe-buffer "5.1.1"
|
||||||
|
send "0.16.2"
|
||||||
|
serve-static "1.13.2"
|
||||||
|
setprototypeof "1.1.0"
|
||||||
|
statuses "~1.4.0"
|
||||||
|
type-is "~1.6.16"
|
||||||
|
utils-merge "1.0.1"
|
||||||
|
vary "~1.1.2"
|
||||||
|
|
||||||
extend-shallow@^2.0.1:
|
extend-shallow@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
|
resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
|
||||||
@@ -3813,6 +3976,18 @@ finalhandler@1.1.0:
|
|||||||
statuses "~1.3.1"
|
statuses "~1.3.1"
|
||||||
unpipe "~1.0.0"
|
unpipe "~1.0.0"
|
||||||
|
|
||||||
|
finalhandler@1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105"
|
||||||
|
dependencies:
|
||||||
|
debug "2.6.9"
|
||||||
|
encodeurl "~1.0.2"
|
||||||
|
escape-html "~1.0.3"
|
||||||
|
on-finished "~2.3.0"
|
||||||
|
parseurl "~1.3.2"
|
||||||
|
statuses "~1.4.0"
|
||||||
|
unpipe "~1.0.0"
|
||||||
|
|
||||||
find-cache-dir@^1.0.0:
|
find-cache-dir@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f"
|
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f"
|
||||||
@@ -4123,6 +4298,16 @@ getpass@^0.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
assert-plus "^1.0.0"
|
assert-plus "^1.0.0"
|
||||||
|
|
||||||
|
gigya@2.0.33:
|
||||||
|
version "2.0.33"
|
||||||
|
resolved "https://registry.yarnpkg.com/gigya/-/gigya-2.0.33.tgz#c5845cd16fac8ebcfb5e727e1ebe9e51352482fb"
|
||||||
|
dependencies:
|
||||||
|
"@types/lodash" "^4.14.50"
|
||||||
|
"@types/node" "^7.0.0"
|
||||||
|
"@types/request" "^0.0.39"
|
||||||
|
lodash "^4.17.4"
|
||||||
|
request "^2.79.0"
|
||||||
|
|
||||||
git-up@^2.0.0:
|
git-up@^2.0.0:
|
||||||
version "2.0.9"
|
version "2.0.9"
|
||||||
resolved "https://registry.yarnpkg.com/git-up/-/git-up-2.0.9.tgz#219bfd27c82daeead8495beb386dc18eae63636d"
|
resolved "https://registry.yarnpkg.com/git-up/-/git-up-2.0.9.tgz#219bfd27c82daeead8495beb386dc18eae63636d"
|
||||||
@@ -5119,6 +5304,10 @@ ipaddr.js@1.5.2:
|
|||||||
version "1.5.2"
|
version "1.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0"
|
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0"
|
||||||
|
|
||||||
|
ipaddr.js@1.6.0:
|
||||||
|
version "1.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.6.0.tgz#e3fa357b773da619f26e95f049d055c72796f86b"
|
||||||
|
|
||||||
is-absolute-url@^2.0.0:
|
is-absolute-url@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6"
|
resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6"
|
||||||
@@ -6975,6 +7164,10 @@ merge@^1.1.3:
|
|||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da"
|
resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da"
|
||||||
|
|
||||||
|
mersenne-twister@^1.0.1:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mersenne-twister/-/mersenne-twister-1.1.0.tgz#f916618ee43d7179efcf641bec4531eb9670978a"
|
||||||
|
|
||||||
metascraper-author@^3.9.2:
|
metascraper-author@^3.9.2:
|
||||||
version "3.9.2"
|
version "3.9.2"
|
||||||
resolved "https://registry.yarnpkg.com/metascraper-author/-/metascraper-author-3.9.2.tgz#ff2020ac428f59a875d655df3b0d4bea171fde19"
|
resolved "https://registry.yarnpkg.com/metascraper-author/-/metascraper-author-3.9.2.tgz#ff2020ac428f59a875d655df3b0d4bea171fde19"
|
||||||
@@ -7115,7 +7308,7 @@ miller-rabin@^4.0.0:
|
|||||||
version "1.30.0"
|
version "1.30.0"
|
||||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01"
|
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01"
|
||||||
|
|
||||||
"mime-db@>= 1.33.0 < 2":
|
"mime-db@>= 1.33.0 < 2", mime-db@~1.33.0:
|
||||||
version "1.33.0"
|
version "1.33.0"
|
||||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db"
|
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db"
|
||||||
|
|
||||||
@@ -7125,6 +7318,12 @@ mime-types@^2.1.10, mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16,
|
|||||||
dependencies:
|
dependencies:
|
||||||
mime-db "~1.30.0"
|
mime-db "~1.30.0"
|
||||||
|
|
||||||
|
mime-types@~2.1.18:
|
||||||
|
version "2.1.18"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8"
|
||||||
|
dependencies:
|
||||||
|
mime-db "~1.33.0"
|
||||||
|
|
||||||
mime@1.4.1, mime@^1.3.4, mime@^1.4.1:
|
mime@1.4.1, mime@^1.3.4, mime@^1.4.1:
|
||||||
version "1.4.1"
|
version "1.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
|
resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
|
||||||
@@ -7257,6 +7456,10 @@ moment@^2.10.3:
|
|||||||
version "2.19.1"
|
version "2.19.1"
|
||||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.1.tgz#56da1a2d1cbf01d38b7e1afc31c10bcfa1929167"
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.1.tgz#56da1a2d1cbf01d38b7e1afc31c10bcfa1929167"
|
||||||
|
|
||||||
|
moment@^2.15.2:
|
||||||
|
version "2.22.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.0.tgz#7921ade01017dd45186e7fee5f424f0b8663a730"
|
||||||
|
|
||||||
mongodb-core@2.1.17:
|
mongodb-core@2.1.17:
|
||||||
version "2.1.17"
|
version "2.1.17"
|
||||||
resolved "https://registry.yarnpkg.com/mongodb-core/-/mongodb-core-2.1.17.tgz#a418b337a14a14990fb510b923dee6a813173df8"
|
resolved "https://registry.yarnpkg.com/mongodb-core/-/mongodb-core-2.1.17.tgz#a418b337a14a14990fb510b923dee6a813173df8"
|
||||||
@@ -7294,7 +7497,7 @@ moo-server@*, moo-server@1.3.x:
|
|||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/moo-server/-/moo-server-1.3.0.tgz#5dc79569565a10d6efed5439491e69d2392e58f1"
|
resolved "https://registry.yarnpkg.com/moo-server/-/moo-server-1.3.0.tgz#5dc79569565a10d6efed5439491e69d2392e58f1"
|
||||||
|
|
||||||
morgan@^1.6.1, morgan@^1.9.0:
|
morgan@^1.6.1:
|
||||||
version "1.9.0"
|
version "1.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.9.0.tgz#d01fa6c65859b76fcf31b3cb53a3821a311d8051"
|
resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.9.0.tgz#d01fa6c65859b76fcf31b3cb53a3821a311d8051"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -8214,6 +8417,15 @@ passport-oauth2@1.x.x, passport-oauth2@^1.1.2:
|
|||||||
uid2 "0.0.x"
|
uid2 "0.0.x"
|
||||||
utils-merge "1.x.x"
|
utils-merge "1.x.x"
|
||||||
|
|
||||||
|
passport-openidconnect@^0.0.2:
|
||||||
|
version "0.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/passport-openidconnect/-/passport-openidconnect-0.0.2.tgz#e488f8bdb386c9a9fd39c91d5ab8c880156e8153"
|
||||||
|
dependencies:
|
||||||
|
oauth "0.9.x"
|
||||||
|
passport-strategy "1.x.x"
|
||||||
|
request "^2.75.0"
|
||||||
|
webfinger "0.4.x"
|
||||||
|
|
||||||
passport-strategy@1.x.x, passport-strategy@^1.0.0:
|
passport-strategy@1.x.x, passport-strategy@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4"
|
resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4"
|
||||||
@@ -8938,6 +9150,13 @@ proxy-addr@~2.0.2:
|
|||||||
forwarded "~0.1.2"
|
forwarded "~0.1.2"
|
||||||
ipaddr.js "1.5.2"
|
ipaddr.js "1.5.2"
|
||||||
|
|
||||||
|
proxy-addr@~2.0.3:
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341"
|
||||||
|
dependencies:
|
||||||
|
forwarded "~0.1.2"
|
||||||
|
ipaddr.js "1.6.0"
|
||||||
|
|
||||||
proxy-agent@2.0.0:
|
proxy-agent@2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-2.0.0.tgz#57eb5347aa805d74ec681cb25649dba39c933499"
|
resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-2.0.0.tgz#57eb5347aa805d74ec681cb25649dba39c933499"
|
||||||
@@ -9188,6 +9407,10 @@ randexp@^0.4.2:
|
|||||||
discontinuous-range "1.0.0"
|
discontinuous-range "1.0.0"
|
||||||
ret "~0.1.10"
|
ret "~0.1.10"
|
||||||
|
|
||||||
|
random-bytes@~1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b"
|
||||||
|
|
||||||
randomatic@^1.1.3:
|
randomatic@^1.1.3:
|
||||||
version "1.1.7"
|
version "1.1.7"
|
||||||
resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c"
|
resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c"
|
||||||
@@ -9658,6 +9881,10 @@ regexp-clone@0.0.1:
|
|||||||
version "0.0.1"
|
version "0.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/regexp-clone/-/regexp-clone-0.0.1.tgz#a7c2e09891fdbf38fbb10d376fb73003e68ac589"
|
resolved "https://registry.yarnpkg.com/regexp-clone/-/regexp-clone-0.0.1.tgz#a7c2e09891fdbf38fbb10d376fb73003e68ac589"
|
||||||
|
|
||||||
|
regexpp@^1.0.1:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-1.1.0.tgz#0e3516dd0b7904f413d2d4193dce4618c3a689ab"
|
||||||
|
|
||||||
regexpu-core@^1.0.0:
|
regexpu-core@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b"
|
resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b"
|
||||||
@@ -9808,6 +10035,33 @@ request@2.81.0:
|
|||||||
tunnel-agent "^0.6.0"
|
tunnel-agent "^0.6.0"
|
||||||
uuid "^3.0.0"
|
uuid "^3.0.0"
|
||||||
|
|
||||||
|
request@^2.75.0:
|
||||||
|
version "2.85.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/request/-/request-2.85.0.tgz#5a03615a47c61420b3eb99b7dba204f83603e1fa"
|
||||||
|
dependencies:
|
||||||
|
aws-sign2 "~0.7.0"
|
||||||
|
aws4 "^1.6.0"
|
||||||
|
caseless "~0.12.0"
|
||||||
|
combined-stream "~1.0.5"
|
||||||
|
extend "~3.0.1"
|
||||||
|
forever-agent "~0.6.1"
|
||||||
|
form-data "~2.3.1"
|
||||||
|
har-validator "~5.0.3"
|
||||||
|
hawk "~6.0.2"
|
||||||
|
http-signature "~1.2.0"
|
||||||
|
is-typedarray "~1.0.0"
|
||||||
|
isstream "~0.1.2"
|
||||||
|
json-stringify-safe "~5.0.1"
|
||||||
|
mime-types "~2.1.17"
|
||||||
|
oauth-sign "~0.8.2"
|
||||||
|
performance-now "^2.1.0"
|
||||||
|
qs "~6.5.1"
|
||||||
|
safe-buffer "^5.1.1"
|
||||||
|
stringstream "~0.0.5"
|
||||||
|
tough-cookie "~2.3.3"
|
||||||
|
tunnel-agent "^0.6.0"
|
||||||
|
uuid "^3.1.0"
|
||||||
|
|
||||||
require-directory@^2.1.1:
|
require-directory@^2.1.1:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
|
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
|
||||||
@@ -10022,7 +10276,7 @@ sax@0.5.x:
|
|||||||
version "0.5.8"
|
version "0.5.8"
|
||||||
resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1"
|
resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1"
|
||||||
|
|
||||||
sax@^1.1.4, sax@^1.2.1, sax@^1.2.4, sax@~1.2.1:
|
sax@>=0.1.1, sax@^1.1.4, sax@^1.2.1, sax@^1.2.4, sax@~1.2.1:
|
||||||
version "1.2.4"
|
version "1.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
||||||
|
|
||||||
@@ -10161,7 +10415,7 @@ serve-static@1.13.0:
|
|||||||
parseurl "~1.3.2"
|
parseurl "~1.3.2"
|
||||||
send "0.16.0"
|
send "0.16.0"
|
||||||
|
|
||||||
serve-static@^1.10.0:
|
serve-static@1.13.2, serve-static@^1.10.0:
|
||||||
version "1.13.2"
|
version "1.13.2"
|
||||||
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1"
|
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -10578,6 +10832,10 @@ stealthy-require@^1.1.0:
|
|||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b"
|
resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b"
|
||||||
|
|
||||||
|
step@0.0.x:
|
||||||
|
version "0.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/step/-/step-0.0.6.tgz#143e7849a5d7d3f4a088fe29af94915216eeede2"
|
||||||
|
|
||||||
stream-browserify@^2.0.1:
|
stream-browserify@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db"
|
resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db"
|
||||||
@@ -10875,7 +11133,7 @@ symbol-observable@^1.0.2, symbol-observable@^1.0.3, symbol-observable@^1.0.4:
|
|||||||
version "3.2.2"
|
version "3.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"
|
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"
|
||||||
|
|
||||||
table@^4.0.1:
|
table@4.0.2, table@^4.0.1:
|
||||||
version "4.0.2"
|
version "4.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36"
|
resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -11195,6 +11453,13 @@ type-is@~1.6.15:
|
|||||||
media-typer "0.3.0"
|
media-typer "0.3.0"
|
||||||
mime-types "~2.1.15"
|
mime-types "~2.1.15"
|
||||||
|
|
||||||
|
type-is@~1.6.16:
|
||||||
|
version "1.6.16"
|
||||||
|
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194"
|
||||||
|
dependencies:
|
||||||
|
media-typer "0.3.0"
|
||||||
|
mime-types "~2.1.18"
|
||||||
|
|
||||||
typedarray@^0.0.6:
|
typedarray@^0.0.6:
|
||||||
version "0.0.6"
|
version "0.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||||
@@ -11269,6 +11534,12 @@ uid-number@^0.0.6:
|
|||||||
version "0.0.6"
|
version "0.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
|
resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
|
||||||
|
|
||||||
|
uid-safe@~2.1.5:
|
||||||
|
version "2.1.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a"
|
||||||
|
dependencies:
|
||||||
|
random-bytes "~1.0.0"
|
||||||
|
|
||||||
uid2@0.0.x:
|
uid2@0.0.x:
|
||||||
version "0.0.3"
|
version "0.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82"
|
resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82"
|
||||||
@@ -11559,6 +11830,13 @@ watchpack@^1.4.0:
|
|||||||
chokidar "^1.7.0"
|
chokidar "^1.7.0"
|
||||||
graceful-fs "^4.1.2"
|
graceful-fs "^4.1.2"
|
||||||
|
|
||||||
|
webfinger@0.4.x:
|
||||||
|
version "0.4.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/webfinger/-/webfinger-0.4.2.tgz#3477a6d97799461896039fcffc650b73468ee76d"
|
||||||
|
dependencies:
|
||||||
|
step "0.0.x"
|
||||||
|
xml2js "0.1.x"
|
||||||
|
|
||||||
webidl-conversions@^2.0.0:
|
webidl-conversions@^2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-2.0.1.tgz#3bf8258f7d318c7443c36f2e169402a1a6703506"
|
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-2.0.1.tgz#3bf8258f7d318c7443c36f2e169402a1a6703506"
|
||||||
@@ -11795,6 +12073,12 @@ xml-name-validator@^3.0.0:
|
|||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
|
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
|
||||||
|
|
||||||
|
xml2js@0.1.x:
|
||||||
|
version "0.1.14"
|
||||||
|
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.1.14.tgz#5274e67f5a64c5f92974cd85139e0332adc6b90c"
|
||||||
|
dependencies:
|
||||||
|
sax ">=0.1.1"
|
||||||
|
|
||||||
xml@^1.0.0, xml@^1.0.1:
|
xml@^1.0.0, xml@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5"
|
resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5"
|
||||||
|
|||||||
Reference in New Issue
Block a user