mirror of
https://github.com/wassname/talk.git
synced 2026-09-09 11:38:08 +08:00
Added BE for setup
- Adjusted docs - Fixed issues with process terminating wrong
This commit is contained in:
+7
-9
@@ -1,9 +1,10 @@
|
||||
# Contributor's Guide
|
||||
|
||||
Welcome! We are very excited that you are interested in contributing to Talk.
|
||||
Welcome! We are very excited that you are interested in contributing to Talk.
|
||||
|
||||
This document is a companion to help you approach contributing. If it does not do so, please [let us know how we can improve it](https://github.com/coralproject/talk/issues)!
|
||||
|
||||
By contributing to this project you agree to the [Code of Conduct](https://coralproject.net/code-of-conduct.html).
|
||||
|
||||
## Product Roadmap
|
||||
|
||||
@@ -13,7 +14,7 @@ You can view product ideas and our longer term roadmap here https://trello.com/b
|
||||
|
||||
|
||||
## Contribute to the documentation
|
||||
Clear docs are a prerequisite for a successful open source project. We value non-code and code contributions equally.
|
||||
Clear docs are a prerequisite for a successful open source project. We value non-code and code contributions equally.
|
||||
|
||||
We are looking for _documentarians_ to:
|
||||
|
||||
@@ -40,7 +41,7 @@ Talk is designed to integrate into existing environments in a variety of ways:
|
||||
|
||||
If you're considering deploying Talk, [please let us know](https://github.com/coralproject/talk/wiki/Contact-Us)! We are quite literally doing this for you and want to help you succeed any way we can.
|
||||
|
||||
If you are writing custom integration code in your fork of Talk, please consider keeping it generic and filing a Pull Request to contribute it back to the project! See our [forking and merging guidelines](https://github.com/coralproject/talk/wiki/Forking,-Branching-and-Merging) for more info.
|
||||
If you are writing custom integration code in your fork of Talk, please consider keeping it generic and filing a Pull Request to contribute it back to the project! See our [forking and merging guidelines](https://github.com/coralproject/talk/wiki/Forking,-Branching-and-Merging) for more info.
|
||||
|
||||
## Write some code
|
||||
|
||||
@@ -48,9 +49,9 @@ First, [set up a dev environment](https://github.com/coralproject/talk/blob/mast
|
||||
|
||||
### Build a New Feature / Plugin
|
||||
|
||||
Talk is beginning life as a Commenting Platform, but is architected to support many varieties of community engagement.
|
||||
Talk is beginning life as a Commenting Platform, but is architected to support many varieties of community engagement.
|
||||
|
||||
Please [contact us](https://github.com/coralproject/talk/wiki/Contact-Us) early and often if you'd like to help. We would love to hear your ideas for features and plugins and help you find a way to productively engage the project.
|
||||
Please [contact us](https://github.com/coralproject/talk/wiki/Contact-Us) early and often if you'd like to help. We would love to hear your ideas for features and plugins and help you find a way to productively engage the project.
|
||||
|
||||
To get an idea of where the Coral Team is going, see:
|
||||
|
||||
@@ -66,13 +67,10 @@ Examples:
|
||||
|
||||
### Work on the Core
|
||||
|
||||
There is always more work to be done to make an application more stable, scaleable and secure.
|
||||
There is always more work to be done to make an application more stable, scaleable and secure.
|
||||
|
||||
If you see issues in the code or have ideas on how we may improve Talk, please consider:
|
||||
|
||||
* [contributing a fix](https://github.com/coralproject/talk/wiki/Forking,-Branching-and-Merging),
|
||||
* [filing an issue](https://github.com/coralproject/talk/issues), or
|
||||
* or otherwise [letting us know](https://github.com/coralproject/talk/wiki/Contact-Us).
|
||||
|
||||
|
||||
|
||||
|
||||
+60
-61
@@ -1,95 +1,94 @@
|
||||
# Installing a dev environment
|
||||
|
||||
By contributing to this project you agree to the [Code of Conduct](https://coralproject.net/code-of-conduct.html).
|
||||
# Installation
|
||||
|
||||
## Requirements
|
||||
|
||||
### System
|
||||
|
||||
- Any flavor of Linux, OSX or Windows
|
||||
- Any flavour of Linux, OSX or Windows
|
||||
- 1GB memory (minimum)
|
||||
- 5GB storage (minimum)
|
||||
|
||||
### Software
|
||||
## Installation From Source
|
||||
|
||||
* [Node](https://nodejs.org/es/download/package-manager) v7 or later
|
||||
* Mongo v3.2 or later
|
||||
* Redis v3.2 or later
|
||||
### Requirements
|
||||
|
||||
There are some runtime requirements for running Talk from source:
|
||||
|
||||
- [Node](https://nodejs.org/) v7 or later
|
||||
- [MongoDB](https://www.mongodb.com/) v3.2 or later
|
||||
- [Redis](https://redis.io/) v3.2 or later
|
||||
- [Yarn](https://yarnpkg.com/) v0.19.1 or later
|
||||
|
||||
_Please be sure to check the versions of these requirements. Insufficient versions of these may lead to unexpected errors!_
|
||||
|
||||
## First time setup
|
||||
### Installing
|
||||
|
||||
### Installation
|
||||
```bash
|
||||
# Download the tarball containing the repository
|
||||
curl -L https://github.com/coralproject/talk/tarball/master -o coralproject-talk.tar.gz
|
||||
|
||||
Navigate to a directory.
|
||||
# Untar that file and change to that directory
|
||||
tar xpf coralproject-talk.tar.gz
|
||||
mv coralproject-talk-* coralproject-talk
|
||||
cd coralproject-talk
|
||||
|
||||
```
|
||||
git clone https://github.com/coralproject/talk
|
||||
cd talk
|
||||
yarn install
|
||||
```
|
||||
# Install package dependancies
|
||||
yarn
|
||||
|
||||
### Environmental Variables
|
||||
|
||||
Talk uses environmental variables for configuration. You can learn about them in the [README file](README.md).
|
||||
|
||||
|
||||
## Workflows
|
||||
|
||||
### The server
|
||||
|
||||
Starting the server:
|
||||
|
||||
```
|
||||
yarn start
|
||||
```
|
||||
|
||||
Browse to `http://localhost:3000` (or your custom port.)
|
||||
|
||||
### Building the front end
|
||||
|
||||
Our build process will build all front end components registered [here](https://github.com/coralproject/talk/blob/6052cac1d3494f8060325a88bb2ce03c88c2f94c/webpack.config.dev.js#L9-L15).
|
||||
|
||||
One time build:
|
||||
|
||||
```
|
||||
# Build static files
|
||||
yarn build
|
||||
```
|
||||
|
||||
Build, then rebuild when a file is updated (development build):
|
||||
### Running
|
||||
|
||||
```
|
||||
yarn build-watch
|
||||
Refer to the `README.md` file for required configuration variables to add to the
|
||||
environment.
|
||||
|
||||
You can start the server after configuring the server using the command:
|
||||
|
||||
```bash
|
||||
yarn start
|
||||
```
|
||||
|
||||
You can see other scripts we've made available by consulting the `package.json`
|
||||
file under the `scripts` key including:
|
||||
|
||||
### Testing
|
||||
- `yarn test` run unit tests
|
||||
- `yarn e2e` run end to end tests
|
||||
- `yarn build-watch` watch for changes to client files and build static assets
|
||||
- `yarn dev-start` watch for changes to server files and reload the server
|
||||
|
||||
Run all tests once:
|
||||
## Installation From Docker Hub
|
||||
|
||||
`
|
||||
yarn test
|
||||
`
|
||||
### Requirements
|
||||
|
||||
Run our end to end tests (will install Selenium and nightwatch):
|
||||
There are some runtime requirements for running Talk for Docker:
|
||||
|
||||
`
|
||||
yarn e2e
|
||||
`
|
||||
- [MongoDB](https://www.mongodb.com/) v3.2 or later
|
||||
- [Redis](https://redis.io/) v3.2 or later
|
||||
- [Docker](https://www.docker.com/) v1.13.0 or later
|
||||
- [Docker Compose](https://docs.docker.com/compose/) v1.10.0 or later
|
||||
|
||||
_Please ensure all tests are passing before submitting a PR!_
|
||||
_Please be sure to check the versions of these requirements. Insufficient versions of these may lead to unexpected errors!_
|
||||
|
||||
## Troubleshooting
|
||||
### Installing
|
||||
|
||||
```bash
|
||||
# Create a directory for talk
|
||||
mkdir coralproject-talk
|
||||
cd coralproject-talk
|
||||
|
||||
##### Can't ping the redis server!
|
||||
# Download the docker-compose.yml file from the repository
|
||||
curl -LO https://raw.githubusercontent.com/coralproject/talk/master/docker-compose.yml
|
||||
```
|
||||
|
||||
- Check that Redis Server is running.
|
||||
- Check that TALK_REDIS_URL is set.
|
||||
|
||||
##### Authenticaiton doesn't work!
|
||||
|
||||
- Make sure Redis is the correct version.
|
||||
At this stage, you should refer to the `README.md` file for required
|
||||
configuration variables to add to the environment key for the `talk` service
|
||||
listed in the `docker-compose.yml` file.
|
||||
|
||||
### Running
|
||||
|
||||
```bash
|
||||
# Start the services using compose
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
@@ -12,7 +12,7 @@ See our [Contribution Guide](https://github.com/coralproject/talk/blob/master/CO
|
||||
|
||||
To set up a development environment or build from source, see [INSTALL.md](https://github.com/coralproject/talk/blob/master/INSTALL.md).
|
||||
|
||||
To launch a Talk server of your own from your browser without any need to muck about in a terminal or think about engineering concepts, stay tuned. We will launch [our installer](https://github.com/coralproject/talk-install) shortly!!
|
||||
To launch a Talk server of your own from your browser without any need to muck about in a terminal or think about engineering concepts, stay tuned. We will launch [our installer](https://github.com/coralproject/talk-install) shortly!
|
||||
|
||||
|
||||
### Configuration
|
||||
@@ -37,6 +37,7 @@ Facebook Login enabled app.
|
||||
- `TALK_SMTP_PASSWORD` (*required for email*) - password for the SMTP provider you are using.
|
||||
- `TALK_SMTP_HOST` (*required for email*) - SMTP host url with format `smtp.domain.com`.
|
||||
- `TALK_SMTP_PORT` (*required for email*) - SMTP port.
|
||||
- `TALK_INSTALL_LOCK` (_optional for dynamic setup_) - Defaults to `FALSE`. When `TRUE`, disables the dynamic setup endpoint.
|
||||
|
||||
Refer to the wiki page on [Configuration Loading](https://github.com/coralproject/talk/wiki/Configuration-Loading) for
|
||||
alternative methods of loading configuration during development.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
const util = require('../util');
|
||||
// const util = require('./util');
|
||||
const program = require('./commander');
|
||||
|
||||
program
|
||||
@@ -15,18 +15,14 @@ program
|
||||
.command('users', 'work with the application auth')
|
||||
.parse(process.argv);
|
||||
|
||||
// If there is no command listed, output help.
|
||||
if (!process.argv.slice(2).length) {
|
||||
program.outputHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
// The ensures that the child process that is created here is always cleaned up
|
||||
// properly when the parent process dies.
|
||||
util.onshutdown([
|
||||
(signal) => {
|
||||
if ((program.runningCommand.killed === false) && (program.runningCommand.exitCode === null)) {
|
||||
program.runningCommand.kill(signal);
|
||||
}
|
||||
/**
|
||||
* When this provess exists, check to see if we have a running command, if we do
|
||||
* check to see if it is still running. If it is, then kill it with a SIGINT
|
||||
* signal. This is for the use case where we want to kill the process that is
|
||||
* labled with the PID written out by the parent process.
|
||||
*/
|
||||
process.once('exit', () => {
|
||||
if ((program.runningCommand.killed === false) && (program.runningCommand.exitCode === null)) {
|
||||
program.runningCommand.kill('SIGINT');
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ const Table = require('cli-table');
|
||||
const AssetModel = require('../models/asset');
|
||||
const mongoose = require('../services/mongoose');
|
||||
const scraper = require('../services/scraper');
|
||||
const util = require('../util');
|
||||
const util = require('./util');
|
||||
|
||||
// Register the shutdown criteria.
|
||||
util.onshutdown([
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
const program = require('./commander');
|
||||
const scraper = require('../services/scraper');
|
||||
const mailer = require('../services/mailer');
|
||||
const util = require('../util');
|
||||
const util = require('./util');
|
||||
const mongoose = require('../services/mongoose');
|
||||
const kue = require('../services/kue');
|
||||
|
||||
|
||||
+2
-3
@@ -2,13 +2,12 @@
|
||||
|
||||
const app = require('../app');
|
||||
const program = require('./commander');
|
||||
const debug = require('debug')('talk:server');
|
||||
const http = require('http');
|
||||
const scraper = require('../services/scraper');
|
||||
const mailer = require('../services/mailer');
|
||||
const kue = require('../services/kue');
|
||||
const mongoose = require('../services/mongoose');
|
||||
const util = require('../util');
|
||||
const util = require('./util');
|
||||
|
||||
/**
|
||||
* Get port from environment and store in Express.
|
||||
@@ -79,7 +78,7 @@ function onListening() {
|
||||
let bind = typeof addr === 'string'
|
||||
? `pipe ${ addr}`
|
||||
: `port ${ addr.port}`;
|
||||
debug(`Listening on ${ bind}`);
|
||||
console.log(`Listening on ${ bind}`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+148
-50
@@ -9,7 +9,10 @@ const inquirer = require('inquirer');
|
||||
const mongoose = require('../services/mongoose');
|
||||
const SettingModel = require('../models/setting');
|
||||
const SettingsService = require('../services/settings');
|
||||
const util = require('../util');
|
||||
const SetupService = require('../services/setup');
|
||||
const UsersService = require('../services/users');
|
||||
const util = require('./util');
|
||||
const errors = require('../errors');
|
||||
|
||||
// Register the shutdown criteria.
|
||||
util.onshutdown([
|
||||
@@ -29,60 +32,155 @@ program
|
||||
// Setup the application
|
||||
//==============================================================================
|
||||
|
||||
SettingsService
|
||||
.init()
|
||||
.then((settings) => {
|
||||
if (program.defaults) {
|
||||
return settings.save();
|
||||
}
|
||||
const performSetup = () => {
|
||||
|
||||
console.log('We\'ll ask you some questions in order to setup your installation of Talk.\n');
|
||||
if (program.defaults) {
|
||||
return SettingsService.init();
|
||||
}
|
||||
|
||||
return inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'organizationName',
|
||||
message: 'Organization Name',
|
||||
default: settings.organizationName,
|
||||
validate: (input) => {
|
||||
if (input && input.length > 0) {
|
||||
return true;
|
||||
}
|
||||
// Get the current settings, we are expecing an error here.
|
||||
return SettingsService
|
||||
.retrieve()
|
||||
.then(() => {
|
||||
|
||||
return 'Organization Name is required.';
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
choices: SettingModel.MODERATION_OPTIONS,
|
||||
name: 'moderation',
|
||||
default: settings.moderation,
|
||||
message: 'Select a moderation mode'
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'requireEmailConfirmation',
|
||||
default: settings.requireEmailConfirmation,
|
||||
message: 'Should emails always be confirmed'
|
||||
// We should NOT have gotten a settings object, this means that the
|
||||
// application is already setup. Error out here.
|
||||
throw errors.ErrSettingsInit;
|
||||
|
||||
})
|
||||
.catch((err) => {
|
||||
|
||||
// If the error is `not init`, then we're good, otherwise, it's something
|
||||
// else.
|
||||
if (err !== errors.ErrSettingsNotInit) {
|
||||
throw err;
|
||||
}
|
||||
])
|
||||
.then((answers) => {
|
||||
|
||||
// Update the settings that were changed.
|
||||
Object.keys(answers).forEach((key) => {
|
||||
if (answers[key] !== undefined) {
|
||||
settings[key] = answers[key];
|
||||
})
|
||||
.then(() => {
|
||||
|
||||
// Create the base settings model.
|
||||
let settings = new SettingModel();
|
||||
|
||||
console.log('We\'ll ask you some questions in order to setup your installation of Talk.\n');
|
||||
|
||||
return inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'organizationName',
|
||||
message: 'Organization Name',
|
||||
default: settings.organizationName,
|
||||
validate: (input) => {
|
||||
if (input && input.length > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return 'Organization Name is required.';
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
choices: SettingModel.MODERATION_OPTIONS,
|
||||
name: 'moderation',
|
||||
default: settings.moderation,
|
||||
message: 'Select a moderation mode'
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'requireEmailConfirmation',
|
||||
default: settings.requireEmailConfirmation,
|
||||
message: 'Should emails always be confirmed'
|
||||
}
|
||||
});
|
||||
])
|
||||
.then((answers) => {
|
||||
|
||||
return settings.save();
|
||||
// Update the settings that were changed.
|
||||
Object.keys(answers).forEach((key) => {
|
||||
if (answers[key] !== undefined) {
|
||||
settings[key] = answers[key];
|
||||
}
|
||||
});
|
||||
|
||||
console.log('\nWe\'ll ask you some questions about your first admin user.\n');
|
||||
|
||||
return inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'displayName',
|
||||
message: 'Display Name',
|
||||
filter: (displayName) => {
|
||||
return UsersService
|
||||
.isValidDisplayName(displayName, false)
|
||||
.catch((err) => {
|
||||
throw err.message;
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
message: 'Email',
|
||||
format: 'email',
|
||||
validate: (value) => {
|
||||
if (value && value.length >= 3) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return 'Email is required';
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'password',
|
||||
message: 'Password',
|
||||
type: 'password',
|
||||
filter: (password) => {
|
||||
return UsersService
|
||||
.isValidPassword(password)
|
||||
.catch((err) => {
|
||||
throw err.message;
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'confirmPassword',
|
||||
message: 'Confirm Password',
|
||||
type: 'password',
|
||||
filter: (confirmPassword) => {
|
||||
return UsersService
|
||||
.isValidPassword(confirmPassword)
|
||||
.catch((err) => {
|
||||
throw err.message;
|
||||
});
|
||||
}
|
||||
},
|
||||
]);
|
||||
})
|
||||
.then((user) => {
|
||||
|
||||
if (user.password !== user.confirmPassword) {
|
||||
return Promise.reject(new Error('Passwords do not match'));
|
||||
}
|
||||
|
||||
return SetupService.setup({
|
||||
settings: settings.toObject(),
|
||||
user: {
|
||||
email: user.email,
|
||||
displayName: user.displayName,
|
||||
password: user.password
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(({user}) => {
|
||||
console.log('Settings created.');
|
||||
console.log(`User ${user.id} created.`);
|
||||
console.log('\nTalk is now installed!');
|
||||
util.shutdown();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
util.shutdown(1);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
console.log('Talk is now installed!');
|
||||
util.shutdown();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
util.shutdown(1);
|
||||
});
|
||||
};
|
||||
|
||||
// Start tthe setup process.
|
||||
performSetup();
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ const inquirer = require('inquirer');
|
||||
const UsersService = require('../services/users');
|
||||
const UserModel = require('../models/user');
|
||||
const mongoose = require('../services/mongoose');
|
||||
const util = require('../util');
|
||||
const util = require('./util');
|
||||
const Table = require('cli-table');
|
||||
|
||||
const validateRequired = (msg = 'Field is required', len = 1) => (input) => {
|
||||
|
||||
+12
-2
@@ -2,7 +2,6 @@ const pkg = require('../package.json');
|
||||
const dotenv = require('dotenv');
|
||||
const fs = require('fs');
|
||||
const program = require('commander');
|
||||
const util = require('../util');
|
||||
|
||||
// Perform rewrites to the runtime environment variables based on the contents
|
||||
// of the process.env.REWRITE_ENV if it exists. This is done here as it is the
|
||||
@@ -27,6 +26,10 @@ const parseArgs = require('minimist')(process.argv.slice(2), {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* If the config flag is present, then we have to load the configuration from
|
||||
* the file specified. We will then load those values into the environment.
|
||||
*/
|
||||
if (parseArgs.config) {
|
||||
let envConfig = dotenv.parse(fs.readFileSync(parseArgs.config, {encoding: 'utf8'}));
|
||||
|
||||
@@ -35,8 +38,15 @@ if (parseArgs.config) {
|
||||
});
|
||||
}
|
||||
|
||||
// If the `--pid` flag is used, put the current pid there.
|
||||
/**
|
||||
* If the pid flag is present, then we have to create a pid file at the location
|
||||
* specified.
|
||||
*/
|
||||
if (parseArgs.pid) {
|
||||
const util = require('./util');
|
||||
|
||||
console.log('Wrote PID');
|
||||
|
||||
util.pid(parseArgs.pid);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
version: '2'
|
||||
services:
|
||||
talk:
|
||||
image: coralproject/talk:latest
|
||||
restart: always
|
||||
ports:
|
||||
- "5000:5000"
|
||||
depends_on:
|
||||
- mongo
|
||||
- redis
|
||||
mongo:
|
||||
image: mongo:3.2
|
||||
restart: always
|
||||
volumes:
|
||||
- mongo:/data/db
|
||||
redis:
|
||||
image: redis:3.2
|
||||
restart: always
|
||||
volumes:
|
||||
- redis:/data
|
||||
volumes:
|
||||
mongo:
|
||||
external: false
|
||||
redis:
|
||||
external: false
|
||||
@@ -128,6 +128,18 @@ const ErrNotAuthorized = new APIError('not authorized', {
|
||||
// initialized.
|
||||
const ErrSettingsNotInit = new Error('settings not initialized, run `./bin/cli setup` to setup the application first');
|
||||
|
||||
// ErrSettingsInit is returned when the setup endpoint is hit and we are already
|
||||
// initialized.
|
||||
const ErrSettingsInit = new APIError('settings are already initialized', {
|
||||
status: 500
|
||||
});
|
||||
|
||||
// ErrInstallLock is returned when the setup endpoint is hit and the install
|
||||
// lock is present.
|
||||
const ErrInstallLock = new APIError('install lock active', {
|
||||
status: 500
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
ExtendableError,
|
||||
APIError,
|
||||
@@ -145,5 +157,7 @@ module.exports = {
|
||||
ErrNotFound,
|
||||
ErrInvalidAssetURL,
|
||||
ErrAuthentication,
|
||||
ErrNotAuthorized
|
||||
ErrNotAuthorized,
|
||||
ErrSettingsInit,
|
||||
ErrInstallLock
|
||||
};
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
const express = require('express');
|
||||
|
||||
const errors = require('../../../errors');
|
||||
|
||||
const SetupService = require('../../../services/setup');
|
||||
const SettingsService = require('../../../services/settings');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.post('/', (req, res, next) => {
|
||||
|
||||
// Check if we have an install lock present.
|
||||
if (process.env.TALK_INSTALL_LOCK === 'TRUE') {
|
||||
return next(errors.ErrInstallLock);
|
||||
}
|
||||
|
||||
// Get the current settings, we are expecing an error here.
|
||||
SettingsService
|
||||
.retrieve()
|
||||
.then(() => {
|
||||
|
||||
// We should NOT have gotten a settings object, this means that the
|
||||
// application is already setup. Error out here.
|
||||
return next(errors.ErrSettingsInit);
|
||||
|
||||
})
|
||||
.catch((err) => {
|
||||
|
||||
// If the error is `not init`, then we're good, otherwise, it's something
|
||||
// else.
|
||||
if (err !== errors.ErrSettingsNotInit) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
// Allow the request to keep going here.
|
||||
return next();
|
||||
});
|
||||
|
||||
}, (req, res, next) => {
|
||||
|
||||
const {
|
||||
settings,
|
||||
user: {email, password, displayName}
|
||||
} = req.body;
|
||||
|
||||
SetupService
|
||||
.setup({settings, user: {email, password, displayName}})
|
||||
.then(() => {
|
||||
|
||||
// We're setup!
|
||||
res.status(204).end();
|
||||
})
|
||||
.catch((err) => {
|
||||
return next(err);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,7 +1,6 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const UsersService = require('../../../services/users');
|
||||
const SettingsService = require('../../../services/settings');
|
||||
const CommentsService = require('../../../services/comments');
|
||||
const mailer = require('../../../services/mailer');
|
||||
const errors = require('../../../errors');
|
||||
@@ -88,12 +87,6 @@ router.post('/:user_id/email', authorization.needed('admin'), (req, res, next) =
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
// /**
|
||||
// * SendEmailConfirmation sends a confirmation email to the user.
|
||||
// * @param {Request} req express request object
|
||||
// * @param {String} email user email address
|
||||
// */
|
||||
|
||||
/**
|
||||
* SendEmailConfirmation sends a confirmation email to the user.
|
||||
* @param {ExpressApp} app the instance of the express app
|
||||
@@ -125,25 +118,14 @@ router.post('/', (req, res, next) => {
|
||||
.createLocalUser(email, password, displayName)
|
||||
.then((user) => {
|
||||
|
||||
// Get the settings from the database to find out if we need to send an
|
||||
// email confirmation. The Front end will know about the
|
||||
// Send an email confirmation. The Front end will know about the
|
||||
// requireEmailConfirmation as it's included in the settings get endpoint.
|
||||
return SettingsService.retrieve().then(({requireEmailConfirmation = false}) => {
|
||||
return SendEmailConfirmation(req.app, user.id, email, redirectUri)
|
||||
.then(() => {
|
||||
|
||||
if (requireEmailConfirmation) {
|
||||
|
||||
SendEmailConfirmation(req.app, user.id, email, redirectUri)
|
||||
.then(() => {
|
||||
|
||||
// Then send back the user.
|
||||
res.status(201).json(user);
|
||||
});
|
||||
} else {
|
||||
|
||||
// We don't need to confirm the email, let's just send back the user!
|
||||
// Then send back the user.
|
||||
res.status(201).json(user);
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
next(err);
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
const UsersService = require('./users');
|
||||
const SettingsService = require('./settings');
|
||||
const SettingsModel = require('../models/setting');
|
||||
const errors = require('../errors');
|
||||
|
||||
/**
|
||||
* This service is used when we want to setup the application. It is consumed by
|
||||
* the dynamic setup endpoint and by the cli-setup tool.
|
||||
*/
|
||||
module.exports = class SetupService {
|
||||
|
||||
/**
|
||||
* This verifies that the current input for the setup is valid.
|
||||
*/
|
||||
static validate({settings, user: {email, displayName, password}}) {
|
||||
|
||||
// Verify the email address of the user.
|
||||
if (!email) {
|
||||
return Promise.reject(errors.ErrMissingEmail);
|
||||
}
|
||||
|
||||
// Create a settings model to use for validation.
|
||||
let settingsModel = new SettingsModel(settings);
|
||||
|
||||
// Verify other properties of the user.
|
||||
return Promise.all([
|
||||
UsersService.isValidDisplayName(displayName, false),
|
||||
UsersService.isValidPassword(password),
|
||||
settingsModel.validate()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will perform the setup.
|
||||
*/
|
||||
static setup({settings, user: {email, password, displayName}}) {
|
||||
|
||||
// Validate the settings first.
|
||||
return SetupService
|
||||
.validate({settings, user: {email, password, displayName}})
|
||||
.then(() => {
|
||||
return SettingsService.update(settings);
|
||||
})
|
||||
.then((settings) => {
|
||||
|
||||
// Settings are created! Create the user.
|
||||
|
||||
// Create the user.
|
||||
return UsersService
|
||||
.createLocalUser(email, password, displayName)
|
||||
|
||||
// Grant them administrative privileges and confirm the email account.
|
||||
.then((user) => {
|
||||
|
||||
return Promise.all([
|
||||
UsersService.addRoleToUser(user.id, 'ADMIN'),
|
||||
UsersService.confirmEmail(user.id, email)
|
||||
])
|
||||
.then(() => ({
|
||||
settings,
|
||||
user
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
+38
-18
@@ -170,10 +170,11 @@ module.exports = class UsersService {
|
||||
|
||||
/**
|
||||
* Check the requested displayname for naughty words (currently in English) and special chars
|
||||
* @param {String} displayName word to be checked for profanity
|
||||
* @param {String} displayName word to be checked for profanity
|
||||
* @param {Boolean} checkAgainstWordlist enables cheching against the wordlist
|
||||
* @return {Promise} rejected if the machine's sensibilites are offended
|
||||
*/
|
||||
static isValidDisplayName(displayName) {
|
||||
static isValidDisplayName(displayName, checkAgainstWordlist = true) {
|
||||
const onlyLettersNumbersUnderscore = /^[A-Za-z0-9_]+$/;
|
||||
|
||||
if (!displayName) {
|
||||
@@ -185,10 +186,19 @@ module.exports = class UsersService {
|
||||
return Promise.reject(errors.ErrSpecialChars);
|
||||
}
|
||||
|
||||
// check for profanity
|
||||
return Wordlist.displayNameCheck(displayName);
|
||||
if (checkAgainstWordlist) {
|
||||
|
||||
// check for profanity
|
||||
return Wordlist.displayNameCheck(displayName);
|
||||
}
|
||||
|
||||
// No errors found!
|
||||
return Promise.resolve(displayName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs validations for the password.
|
||||
*/
|
||||
static isValidPassword(password) {
|
||||
if (!password) {
|
||||
return Promise.reject(errors.ErrMissingPassword);
|
||||
@@ -447,6 +457,8 @@ module.exports = class UsersService {
|
||||
.verifyToken(token, {
|
||||
subject: PASSWORD_RESET_JWT_SUBJECT
|
||||
})
|
||||
|
||||
// TODO: add search by __v as well
|
||||
.then((decoded) => UsersService.findById(decoded.userId));
|
||||
}
|
||||
|
||||
@@ -598,25 +610,33 @@ module.exports = class UsersService {
|
||||
subject: EMAIL_CONFIRM_JWT_SUBJECT
|
||||
})
|
||||
.then(({userID, email, referer}) => {
|
||||
return UserModel
|
||||
.update({
|
||||
id: userID,
|
||||
profiles: {
|
||||
$elemMatch: {
|
||||
id: email,
|
||||
provider: 'local'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
$set: {
|
||||
'profiles.$.metadata.confirmed_at': new Date()
|
||||
}
|
||||
})
|
||||
return UsersService
|
||||
.confirmEmail(userID, email)
|
||||
.then(() => ({userID, email, referer}));
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the email on the user as confirmed.
|
||||
*/
|
||||
static confirmEmail(id, email) {
|
||||
return UserModel
|
||||
.update({
|
||||
id: id,
|
||||
profiles: {
|
||||
$elemMatch: {
|
||||
id: email,
|
||||
provider: 'local'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
$set: {
|
||||
'profiles.$.metadata.confirmed_at': new Date()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all users with pending 'ADMIN'ation actions.
|
||||
* @return {Promise}
|
||||
|
||||
Reference in New Issue
Block a user