Merge branch 'master' into user-sort

This commit is contained in:
Wyatt Johnson
2017-08-18 09:14:19 -06:00
27 changed files with 307 additions and 223 deletions
+35 -157
View File
@@ -3,71 +3,43 @@ const bodyParser = require('body-parser');
const morgan = require('morgan');
const path = require('path');
const helmet = require('helmet');
const authentication = require('./middleware/authentication');
const {passport} = require('./services/passport');
const plugins = require('./services/plugins');
const pubsub = require('./services/pubsub');
const i18n = require('./services/i18n');
const enabled = require('debug').enabled;
const errors = require('./errors');
const {createGraphOptions} = require('./graph');
const apollo = require('graphql-server-express');
const accepts = require('accepts');
const compression = require('compression');
const cookieParser = require('cookie-parser');
const {ROOT_URL} = require('./config');
const {ROOT_URL, ROOT_URL_MOUNT_PATH} = require('./config');
const routes = require('./routes');
const debug = require('debug')('talk:app');
const {URL} = require('url');
const app = express();
// Middleware declarations.
//==============================================================================
// APPLICATION WIDE MIDDLEWARE
//==============================================================================
// Add the logging middleware only if we aren't testing.
if (app.get('env') !== 'test') {
if (process.env.NODE_ENV !== 'test') {
app.use(morgan('dev'));
}
//==============================================================================
// APP MIDDLEWARE
//==============================================================================
// Trust the first proxy in front of us, this will enable us to trust the fact
// that SSL was terminated correctly.
app.set('trust proxy', 1);
// We disable frameward on helmet to allow crossdomain injection of the embed
// Enable a suite of security good practices through helmet. We disable
// frameguard to allow crossdomain injection of the embed.
app.use(helmet({
frameguard: false
frameguard: false,
}));
// Compress the responses if appropriate.
app.use(compression());
// Parse the cookies on the request.
app.use(cookieParser());
// Parse the body json if it's there.
app.use(bodyParser.json());
//==============================================================================
// STATIC FILES
//==============================================================================
// If the application is in production mode, then add gzip rewriting for the
// content.
if (process.env.NODE_ENV === 'production') {
app.get('*.js', (req, res, next) => {
const accept = accepts(req);
if (accept.encoding(['gzip']) === 'gzip') {
// Adjsut the headers on the request by adding a content type header
// because express won't be able to detect the mime-type with the .gz
// extension and we need to decalre support for the gzip encoding.
res.set('Content-Type', 'application/javascript');
res.set('Content-Encoding', 'gzip');
// Rewrite the url so that the gzip version will be served instead.
req.url = `${req.url}.gz`;
}
next();
});
}
app.use('/client', express.static(path.join(__dirname, 'dist')));
app.use('/public', express.static(path.join(__dirname, 'public')));
//==============================================================================
// VIEW CONFIGURATION
//==============================================================================
@@ -75,124 +47,30 @@ app.use('/public', express.static(path.join(__dirname, 'public')));
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
// Set the BASE_URL as the ROOT_URL.
app.locals.BASE_URL = ROOT_URL;
if (app.locals.BASE_URL[app.locals.BASE_URL.length - 1] !== '/') {
app.locals.BASE_URL += '/';
}
//==============================================================================
// PASSPORT MIDDLEWARE
//==============================================================================
const passportDebug = require('debug')('talk:passport');
// Install the passport plugins.
plugins.get('server', 'passport').forEach((plugin) => {
passportDebug(`added plugin '${plugin.plugin.name}'`);
// Pass the passport.js instance to the plugin to allow it to inject it's
// functionality.
plugin.passport(passport);
});
// Setup the PassportJS Middleware.
app.use(passport.initialize());
// Attach the authentication middleware, this will be responsible for decoding
// (if present) the JWT on the request.
app.use('/api', authentication);
const pubsubClient = pubsub.createClientFactory();
// To handle dependancy injection safer, we inject the pubsub handle onto the
// request object.
app.use('/api', (req, res, next) => {
// Attach the pubsub handle to the requests.
req.pubsub = pubsubClient();
// Forward on the request.
next();
});
//==============================================================================
// GraphQL Router
//==============================================================================
// GraphQL endpoint.
app.use('/api/v1/graph/ql', apollo.graphqlExpress(createGraphOptions));
// Only include the graphiql tool if we aren't in production mode.
if (app.get('env') !== 'production') {
// Interactive graphiql interface.
app.use('/api/v1/graph/iql', (req, res) => {
res.render('graphiql', {
endpointURL: '/api/v1/graph/ql'
});
});
// GraphQL documention.
app.get('/admin/docs', (req, res) => {
res.render('admin/docs');
});
}
//==============================================================================
// ROUTES
//==============================================================================
app.use('/', require('./routes'));
// Set the BASE_URL as the ROOT_URL, here we derive the root url by ensuring
// that it ends in a `/`.
const BASE_URL = ROOT_URL && ROOT_URL.length > 0 && ROOT_URL[ROOT_URL.length - 1] === '/' ? ROOT_URL : `${ROOT_URL}/`;
//==============================================================================
// ERROR HANDLING
//==============================================================================
// The BASE_PATH is simply the path component of the BASE_URL.
const BASE_PATH = new URL(BASE_URL).pathname;
// Catch 404 and forward to error handler.
app.use((req, res, next) => {
next(errors.ErrNotFound);
});
// The MOUNT_PATH is derived from the BASE_PATH, if it is provided and enabled.
// This will mount all the application routes onto it.
const MOUNT_PATH = ROOT_URL_MOUNT_PATH ? BASE_PATH : '/';
// General error handler. Respond with the message and error if we have it while
// returning a status code that makes sense.
app.use('/api', (err, req, res, next) => {
if (err !== errors.ErrNotFound) {
if (app.get('env') !== 'test' || enabled('talk:errors')) {
console.error(err);
}
}
// Apply the BASE_PATH, BASE_URL, and MOUNT_PATH on the app.locals, which will
// make them available on the templates and the routers.
app.locals.BASE_URL = BASE_URL;
app.locals.BASE_PATH = BASE_PATH;
app.locals.MOUNT_PATH = MOUNT_PATH;
if (err instanceof errors.APIError) {
res.status(err.status).json({
message: err.message,
error: err
});
} else {
res.status(500).json({});
}
});
debug(`mounting routes on the ${MOUNT_PATH} path`);
app.use('/', (err, req, res, next) => {
if (err !== errors.ErrNotFound) {
console.error(err);
}
i18n.init(req);
if (err instanceof errors.APIError) {
res.status(err.status);
res.render('error', {
message: err.message,
error: app.get('env') === 'development' ? err : {}
});
} else {
res.render('error', {
message: err.message,
error: app.get('env') === 'development' ? err : {}
});
}
});
// Actually apply the routes.
app.use(MOUNT_PATH, routes);
module.exports = app;
@@ -8,14 +8,11 @@ export default ({suspectWords, bannedWords, body, ...rest}) => {
const links = linkify.getMatches(body);
const linkText = links ? links.map((link) => link.raw) : [];
// since words are checked against word boundaries on the backend,
// should be the behavior on the front end as well.
// currently the highlighter plugin does not support out of the box.
const searchWords = [...suspectWords, ...bannedWords]
.filter((w) => {
return new RegExp(`(^|\\s)${w}(\\s|$)`, 'i').test(body);
})
.concat(linkText);
const searchWords = [
...suspectWords,
...bannedWords,
...linkText
];
return (
<Highlighter
+4 -2
View File
@@ -74,8 +74,10 @@ export default function settings (state = initialState, action) {
};
case actions.WORDLIST_UPDATED:
return update(state, {
wordList: {
[action.listName]: {$set: action.list},
wordlist: {
[action.listName]: {
$set: action.list
}
}
});
case actions.DOMAINLIST_UPDATED:
@@ -180,6 +180,9 @@ export default class Comment extends React.Component {
// edit a comment, passed (id, asset_id, { body })
editComment: PropTypes.func,
// emit custom events
emit: PropTypes.func.isRequired,
}
editComment = (...args) => {
@@ -207,7 +210,7 @@ export default class Comment extends React.Component {
}
loadNewReplies = () => {
const {replies, replyCount, id} = this.props.comment;
const {comment: {replies, replyCount, id}, emit} = this.props;
if (replyCount > replies.nodes.length) {
this.setState({loadingState: 'loading'});
this.props.loadMore(id)
@@ -221,10 +224,11 @@ export default class Comment extends React.Component {
this.setState({loadingState: 'error'});
forEachError(error, ({msg}) => {this.props.addNotification('error', msg);});
});
emit('ui.Comment.showMoreReplies', {id});
return;
}
this.setState(resetCursors);
this.props.emit('ui.Comment.showMoreReplies');
emit('ui.Comment.showMoreReplies', {id});
};
showReplyBox = () => {
@@ -315,6 +319,7 @@ export default class Comment extends React.Component {
showSignInDialog,
liveUpdates,
commentIsIgnored,
emit,
commentClassNames = []
} = this.props;
@@ -568,6 +573,7 @@ export default class Comment extends React.Component {
reactKey={reply.id}
key={reply.id}
comment={reply}
emit={emit}
/>;
})}
</TransitionGroup>
@@ -99,7 +99,8 @@ class Stream extends React.Component {
loadMoreComments,
viewAllComments,
auth: {loggedIn, user},
editName
editName,
emit,
} = this.props;
const {keepCommentBox} = this.state;
const open = !asset.isClosed;
@@ -234,6 +235,7 @@ class Stream extends React.Component {
charCountEnable={asset.settings.charCountEnable}
maxCharCount={asset.settings.charCount}
editComment={editComment}
emit={emit}
liveUpdates={true}
/>
</div>
@@ -292,7 +294,7 @@ class Stream extends React.Component {
charCountEnable={asset.settings.charCountEnable}
maxCharCount={asset.settings.charCount}
editComment={editComment}
emit={this.props.emit}
emit={emit}
/>
</TabPane>
</TabContent>
+10 -2
View File
@@ -94,6 +94,10 @@ const CONFIG = {
// The URL for this Talk Instance as viewable from the outside.
ROOT_URL: process.env.TALK_ROOT_URL || null,
// ROOT_URL_MOUNT_PATH when TRUE will extract the pathname from the
// TALK_ROOT_URL and use it to mount the paths on.
ROOT_URL_MOUNT_PATH: process.env.TALK_ROOT_URL_MOUNT_PATH === 'TRUE',
// The keepalive timeout (in ms) that should be used to send keep alive
// messages through the websocket to keep the socket alive.
KEEP_ALIVE: process.env.TALK_KEEP_ALIVE || '30s',
@@ -138,6 +142,10 @@ const CONFIG = {
// CONFIG VALIDATION
//==============================================================================
if (CONFIG.ROOT_URL_MOUNT_PATH && !CONFIG.ROOT_URL) {
throw new Error('TALK_ROOT_URL must be specified if TALK_ROOT_URL_MOUNT_PATH is set to TRUE');
}
if (process.env.NODE_ENV === 'test' && !CONFIG.ROOT_URL) {
CONFIG.ROOT_URL = 'http://localhost:3000';
} else if (!CONFIG.ROOT_URL) {
@@ -188,14 +196,14 @@ CONFIG.JWT_COOKIE_NAMES = uniq(CONFIG.JWT_COOKIE_NAMES.concat([CONFIG.JWT_COOKIE
// External database url's
//------------------------------------------------------------------------------
// Reset the mongo url in the event it hasn't been overrided and we are in a
// Reset the mongo url in the event it hasn't been overridden and we are in a
// testing environment. Every new mongo instance comes with a test database by
// default, this is consistent with common testing and use case practices.
if (process.env.NODE_ENV === 'test' && !CONFIG.MONGO_URL) {
CONFIG.MONGO_URL = 'mongodb://localhost/test';
}
// Reset the redis url in the event it hasn't been overrided and we are in a
// Reset the redis url in the event it hasn't been overridden and we are in a
// testing environment.
if (process.env.NODE_ENV === 'test' && !CONFIG.REDIS_URL) {
CONFIG.REDIS_URL = 'redis://localhost/1';
+52 -17
View File
@@ -5,39 +5,63 @@ permalink: /docs/faq/
### How are new stories/assets added to Talk? Is there an API?
There are three ways that new assets can make their way into Talk: _just in time_, _active_ and manual.
There are three ways that new assets can make their way into Talk:
- Just in time
- Active
- Manual
#### Just in Time asset creation
Talk ships with a _just in time_ mechanism that works out of the box without integration with any CMS or manual work needed.
Talk ships with a _just in time_ mechanism that works out of the box without
integration with any CMS or manual work needed.
The _just in time_ flow looks like this:
* Request comes in for a stream on an asset that doesn't yet exist.
* Talk screens the domain against the domain whitelist, fails if doesn't pass.
* Then, concurrently
* Talk creates a new asset record and returns the stream data (which will be empty)
* Talk creates a new asset record and returns the stream data (which will be
empty)
* Schedules a job to scrape the new page and fill in asset information.
The scraping mechanism utilizes [metascraper](https://www.npmjs.com/package/metascraper) and is queued using the [Que](https://www.npmjs.com/package/kue). If your Talk deployments is configured to run separate job worker cluster, scraping will be performed by them.
The scraping mechanism utilizes
[metascraper](https://www.npmjs.com/package/metascraper) and is queued using the
[Que](https://www.npmjs.com/package/kue). If your Talk deployments is configured
to run separate job worker cluster, scraping will be performed by them.
#### Active (or push based) asset creation
If tighter CMS integration is required to push custom data into assets and/or keep data in sync as changes are made in a CMS an _active_ push based workflow must be implemented.
If tighter CMS integration is required to push custom data into assets and/or
keep data in sync as changes are made in a CMS an _active_ push based workflow
must be implemented.
This is an ideal candidate for a plugin. If you are interested in working on it, please [contact us](https://coralproject.net/contact)!
This is an ideal candidate for a plugin. If you are interested in working on it,
please [contact us](https://coralproject.net/contact)!
#### Manual asset creation
Sometimes you want to load a lot of assets into the database. The most common use case for this is populating the database during an initial installation. We recommend writing a script that transforms the data from it's source and inserts it into the _assets_ collection.
Sometimes you want to load a lot of assets into the database. The most common
use case for this is populating the database during an initial installation. We
recommend writing a script that transforms the data from it's source and inserts
it into the _assets_ collection.
For current schema information, please see the [asset model](https://github.com/coralproject/talk/blob/master/models/asset.js).
For current schema information, please see the
[asset model](https://github.com/coralproject/talk/blob/master/models/asset.js).
### Where are your http API docs?
Coral relies on GraphQL for the vast majority of it's client <-> server communication. All core queries, mutations and subscriptions are defined along with types and comments in our central [TypeDef](https://github.com/coralproject/talk/blob/master/graph/typeDefs.graphql). For plugin graph api typedefs, see each plugin's `/server/` directory.
Coral relies on GraphQL for the vast majority of it's client and server
communication. All core queries, mutations and subscriptions are defined along
with types and comments in our central
[TypeDef](https://github.com/coralproject/talk/blob/master/graph/typeDefs.graphql).
For plugin graph api typedefs, see each plugin's `/server/` directory.
In addition, Talk Server ships with [GraphiQL](https://github.com/graphql/graphiql). GraphiQL provides a full data layer IDE including interactive documentation. The autocompletes and documentation are populated from introspection meaning that Core _and plugin_ apis will be fully explorable.
In addition, Talk Server ships with
[GraphiQL](https://github.com/graphql/graphiql). GraphiQL provides a full data
layer IDE including interactive documentation. The autocomplete and
documentation are populated from introspection meaning that Core _and plugin_
apis can be explored.
To access GraphiQL:
@@ -46,22 +70,33 @@ To access GraphiQL:
### Where is documentation for a specific component?
We strive for clear inline documentation across our codebase, but have gaps. Contributions to documentation would be greatly appreciated and is a great way to start contributing to the project!
We strive for clear inline documentation across our codebase, but have gaps.
Contributions to documentation would be greatly appreciated and is a great way
to start contributing to the project!
If you are considering changing a core component (aka, one that is not in a plugin), you are entering the realm of a core developer. We strongly ask that you reach out the coral team before forking and changing core code. We are glad to help talk through your product need and come up with a strategy for implementing as a plugin, or working with you to extend the plugin API for your use case.
If you are considering changing a core component (aka, one that is not in a
plugin), you are entering the realm of a core developer. We strongly ask that
you reach out the coral team before forking and changing core code. We are glad
to help talk through your product need and come up with a strategy for
implementing as a plugin, or working with you to extend the plugin API for your
use case.
### How do I contribute to these docs?
Contributions to the docs are much appreciated and a great way to get involved in the project.
Contributions to the docs are much appreciated and a great way to get involved
in the project.
Fork the Talk repo, clone it locally (no need to go through the install from source process), then:
Fork the Talk repo, clone it locally (no need to go through the install from
source process), then:
```
cd docs
docker run --rm --volume=$PWD:/srv/jekyll -p 127.0.0.1:4000:4000 -it jekyll/jekyll:pages jekyll serve
```
You can edit the files in docs with any editor and view the live updates in a browser by hitting From the docs directory.
Then visit: [http://127.0.0.1:4000/talk/](http://127.0.0.1:4000/talk/).
You can edit the files in docs with any editor and view the live updates in a
browser by hitting From the docs directory. Then visit:
[http://127.0.0.1:4000/talk/](http://127.0.0.1:4000/talk/).
Once you've made the changes, file a PR back to the `coralproject/talk` repo.
Once you've made the changes, file a PR back to the `coralproject/talk`
repository.
+6 -5
View File
@@ -11,8 +11,9 @@ Available as [coralproject/talk](https://hub.docker.com/r/coralproject/talk/) on
Images are tagged using the following notation:
- `x` (where `x` is the major version number): any minor or patch updates will be included in this. If you're ok getting
new features occasionally and all the bug fixes, this is the tag for you.
- `x` (where `x` is the major version number): any minor or patch updates will
be included in this. If you're ok getting new features occasionally and all
the bug fixes, this is the tag for you.
- `x.y` (where `y` is the minor version number): any patch updates will be
included with this tag. If you like getting fixes and having features change
only when you want, this is the tag for you. **(recommended)**
@@ -42,7 +43,7 @@ An example docker-compose.yml:
version: '2'
services:
talk:
image: coralproject/talk:1.5
image: coralproject/talk:latest
restart: always
ports:
- "5000:5000"
@@ -85,7 +86,7 @@ on different machines. You can achieve this easily with docker compose:
version: '2'
services:
talk-api:
image: coralproject/talk:1.5
image: coralproject/talk:latest
command: cli serve
restart: always
ports:
@@ -97,7 +98,7 @@ services:
- TALK_MONGO_URL=mongodb://mongo/talk
- TALK_REDIS_URL=redis://redis
talk-jobs:
image: coralproject/talk:1.5
image: coralproject/talk:latest
command: cli jobs process
restart: always
ports:
+1 -1
View File
@@ -36,7 +36,7 @@ git clone https://github.com/coralproject/talk.git
We now have to install the dependencies and build the static assets.
```bash
# Install package dependancies
# Install package dependencies
yarn
# Build static files
+1 -1
View File
@@ -70,7 +70,7 @@ independently. Each variety of process can always have just enough resources.
An install that heavily utilizes the jobs queue could see delays in http service
because of heavy jobs processes and/or delays in the execution of jobs processes
due to increased server load as a result of Node's single thread infrustructure.
due to increased server load as a result of Node's single thread infrastructure.
## Deployment Methodologies
+13 -6
View File
@@ -55,11 +55,18 @@ These are only used during the webpack build.
### Server
- `TALK_ROOT_URL` (*required*) - root url of the installed application externally
available in the format: `<scheme>://<host>` without the path.
available in the format: `<scheme>://<host>:<port?>/<pathname>`.
- `TALK_KEEP_ALIVE` (_optional_) - The keepalive timeout that should be used to
send keep alive messages through the websocket to keep the socket alive. (Default `30s`)
- `TALK_INSTALL_LOCK` (_optional for dynamic setup_) - When `TRUE`, disables the dynamic setup endpoint. (Default `FALSE`)
#### Advanced
- `TALK_ROOT_URL_MOUNT_PATH` (_optional_) - when set to `TRUE`, the routes will
be mounted onto the `<pathname>` component of the `TALK_ROOT_URL`. You would
use this when your upstream proxy cannot strip the prefix from the url.
(Default `FALSE`)
### Word Filter
- `TALK_DISABLE_AUTOFLAG_SUSPECT_WORDS` (_optional_) When `TRUE`, disables flagging of comments that match the suspect word filter. (Default `FALSE`)
@@ -118,7 +125,7 @@ will be used:
"iss": TALK_JWT_ISSUER, // *optional* if TALK_JWT_DISABLE_ISSUER === 'TRUE', *required* otherwise
[TALK_JWT_USER_ID_CLAIM]: "<the user id>", // *required* the id of the user
// Note, if TALK_JWT_USER_ID_CLAIM contains '.', it will be used to deliniate an object, for example
// Note, if TALK_JWT_USER_ID_CLAIM contains '.', it will be used to delineate an object, for example
// `user.id` would store it like: `{user: {id}}`
}
```
@@ -151,8 +158,8 @@ order:
### Trust
Trust can automoderate comments based on user history. By specifying this
option, the beheviour can be changed to offer different results.
Trust can auto-moderate comments based on user history. By specifying this
option, the behavior can be changed to offer different results.
- `TRUST_THRESHOLDS` (_optional_) - configure the reliability thresholds for
flagging and commenting. (Default `comment:-1,-1;flag:-1,-1`)
@@ -166,10 +173,10 @@ The form of the environment variable:
The default could be read as:
- When a commenter has one comment rejected, their next comment must be
premoderated once in order to post freely again. If they instead get rejected
pre-moderated once in order to post freely again. If they instead get rejected
again, then they must have two of their comments approved in order to get
added back to the queue.
- At the moment of writing, beheviour is not attached to the flagging
- At the moment of writing, behavior is not attached to the flagging
reliability, but it is recorded.
### Cache
+1 -1
View File
@@ -67,7 +67,7 @@ for more details.
## Authentication Types
Talk also supports two methods of providing authenticationd details.
Talk also supports two methods of providing authentication details.
- Single key: this is used when your secrets do not need to be rotated.
- Multiple keys: this is used when you expect to rotate your secrets.
+1 -1
View File
@@ -31,7 +31,7 @@ It basically consist in having two types of components:
### Container Components
* __How things work__
* They dont have markup nor styles
* They provide data and behaviour to Presentational or Container Components
* They provide data and behavior to Presentational or Container Components
* They connect via `react-redux`s `connect()` to the state.
* They `mapStateToProps` the state to the Presentational Container.
* They `mapDispatchToProps` to send actions to the Presentational Container.
+1 -1
View File
@@ -82,7 +82,7 @@ need to reconcile the plugins and build the static assets:
# get plugin dependancies and remote plugins
./bin/cli plugins reconcile
# build staic assets (including enabled client side plugins)
# build static assets (including enabled client side plugins)
yarn build
```
+1 -1
View File
@@ -236,6 +236,6 @@ Once you've taken this step, anyone can register your plugin into their Talk ser
### Publish to version control
This plugin is open source, so I'm also going to [publish it to github](https://github.com/jde/talk-plugin-asset-manager/commit/66b626caa85cb8030b3ddaa7c1a4821bf01e350a) and [cut a release](https://github.com/jde/talk-plugin-asset-manager/releases/tag/v0.1) that mirrors the npm relese.
This plugin is open source, so I'm also going to [publish it to github](https://github.com/jde/talk-plugin-asset-manager/commit/66b626caa85cb8030b3ddaa7c1a4821bf01e350a) and [cut a release](https://github.com/jde/talk-plugin-asset-manager/releases/tag/v0.1) that mirrors the npm release.
## Done!
+1 -1
View File
@@ -218,7 +218,7 @@ Basic settings can be added via json configuration in a plugin.
* Default value
* Variable name
#### Advanced Custom Configuration (low prioritiy)
#### Advanced Custom Configuration (low priority)
Users can inject configuration interfaces that they create into the configuration allowing for more advanced configuration.
+3 -3
View File
@@ -220,7 +220,7 @@ when a valid token is provided but a user can't be found in the database that
matches the provided id.
The function is async, and should return the user object that was created in the
database, or null if the user wasn't found. The `jwt` paramenter of the object
database, or null if the user wasn't found. The `jwt` parameter of the object
is the unpacked token, while `token` is the original jwt token string.
### Routes
@@ -314,14 +314,14 @@ module.exports = {
const {passport} = require('services/passport');
/**
* Facebook auth endpoint, this will redirect the user immediatly to facebook
* Facebook auth endpoint, this will redirect the user immediately to facebook
* for authorization.
*/
router.get('/facebook', passport.authenticate('facebook', {display: 'popup', authType: 'rerequest', scope: ['public_profile']}));
/**
* Facebook callback endpoint, this will send the user a html page designed to
* send back the user credentials upon sucesfull login.
* send back the user credentials upon successful login.
*/
router.get('/facebook/callback', (req, res, next) => {
+13
View File
@@ -0,0 +1,13 @@
const pubsub = require('../services/pubsub');
const pubsubClient = pubsub.createClientFactory();
// To handle dependancy injection safer, we inject the pubsub handle onto the
// request object.
module.exports = (req, res, next) => {
// Attach the pubsub handle to the requests.
req.pubsub = pubsubClient();
// Forward on the request.
next();
};
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "talk",
"version": "3.1.0",
"version": "3.2.0",
"description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net",
"main": "app.js",
"scripts": {
+139 -3
View File
@@ -2,12 +2,45 @@ const express = require('express');
const path = require('path');
const plugins = require('../services/plugins');
const debug = require('debug')('talk:routes');
const authentication = require('../middleware/authentication');
const {passport} = require('../services/passport');
const pubsub = require('../middleware/pubsub');
const i18n = require('../services/i18n');
const enabled = require('debug').enabled;
const errors = require('../errors');
const {createGraphOptions} = require('../graph');
const accepts = require('accepts');
const apollo = require('graphql-server-express');
const router = express.Router();
router.use('/api/v1', require('./api'));
router.use('/admin', require('./admin'));
router.use('/embed', require('./embed'));
//==============================================================================
// STATIC FILES
//==============================================================================
// If the application is in production mode, then add gzip rewriting for the
// content.
if (process.env.NODE_ENV === 'production') {
router.get('*.js', (req, res, next) => {
const accept = accepts(req);
if (accept.encoding(['gzip']) === 'gzip') {
// Adjsut the headers on the request by adding a content type header
// because express won't be able to detect the mime-type with the .gz
// extension and we need to decalre support for the gzip encoding.
res.set('Content-Type', 'application/javascript');
res.set('Content-Encoding', 'gzip');
// Rewrite the url so that the gzip version will be served instead.
req.url = `${req.url}.gz`;
}
next();
});
}
router.use('/client', express.static(path.join(__dirname, '../dist')));
router.use('/public', express.static(path.join(__dirname, '../public')));
/**
* Serves a file based on a relative path.
@@ -21,6 +54,60 @@ router.get('/embed.js', serveFile('../dist/embed.js'));
router.get('/embed.js.gz', serveFile('../dist/embed.js.gz'));
router.get('/embed.js.map', serveFile('../dist/embed.js.map'));
//==============================================================================
// PASSPORT MIDDLEWARE
//==============================================================================
const passportDebug = require('debug')('talk:passport');
// Install the passport plugins.
plugins.get('server', 'passport').forEach((plugin) => {
passportDebug(`added plugin '${plugin.plugin.name}'`);
// Pass the passport.js instance to the plugin to allow it to inject it's
// functionality.
plugin.passport(passport);
});
// Setup the PassportJS Middleware.
router.use(passport.initialize());
// Attach the authentication middleware, this will be responsible for decoding
// (if present) the JWT on the request.
router.use('/api', authentication, pubsub);
//==============================================================================
// GraphQL Router
//==============================================================================
// GraphQL endpoint.
router.use('/api/v1/graph/ql', apollo.graphqlExpress(createGraphOptions));
// Only include the graphiql tool if we aren't in production mode.
if (process.env.NODE_ENV !== 'production') {
// Interactive graphiql interface.
router.use('/api/v1/graph/iql', (req, res) => {
res.render('graphiql', {
endpointURL: `${req.locals.BASE_URL}api/v1/graph/ql`
});
});
// GraphQL documention.
router.get('/admin/docs', (req, res) => {
res.render('admin/docs');
});
}
//==============================================================================
// ROUTES
//==============================================================================
router.use('/api/v1', require('./api'));
router.use('/admin', require('./admin'));
router.use('/embed', require('./embed'));
if (process.env.NODE_ENV !== 'production') {
router.use('/assets', require('./assets'));
@@ -43,4 +130,53 @@ plugins.get('server', 'router').forEach((plugin) => {
plugin.router(router);
});
//==============================================================================
// ERROR HANDLING
//==============================================================================
// Catch 404 and forward to error handler.
router.use((req, res, next) => {
next(errors.ErrNotFound);
});
// General api error handler. Respond with the message and error if we have it
// while returning a status code that makes sense.
router.use('/api', (err, req, res, next) => {
if (err !== errors.ErrNotFound) {
if (process.env.NODE_ENV !== 'test' || enabled('talk:errors')) {
console.error(err);
}
}
if (err instanceof errors.APIError) {
res.status(err.status).json({
message: err.message,
error: err
});
} else {
res.status(500).json({});
}
});
router.use('/', (err, req, res, next) => {
if (err !== errors.ErrNotFound) {
console.error(err);
}
i18n.init(req);
if (err instanceof errors.APIError) {
res.status(err.status);
res.render('error', {
message: err.message,
error: process.env.NODE_ENV === 'development' ? err : {}
});
} else {
res.render('error', {
message: err.message,
error: process.env.NODE_ENV === 'development' ? err : {}
});
}
});
module.exports = router;
+1 -1
View File
@@ -42,6 +42,6 @@
<body>
<div id="root"></div>
<script src='https://www.google.com/recaptcha/api.js?render=explicit' async defer></script>
<script src="client/coral-admin/bundle.js" charset="utf-8"></script>
<script src="<%= BASE_PATH %>client/coral-admin/bundle.js" charset="utf-8"></script>
</body>
</html>
+1 -1
View File
@@ -71,7 +71,7 @@
$('.error-console').removeClass('active');
$.ajax({
url: '/api/v1/account/email/verify',
url: '<%= BASE_PATH %>api/v1/account/email/verify',
contentType: 'application/json',
method: 'POST',
data: JSON.stringify({token: location.hash.replace('#', '')})
+1 -1
View File
@@ -28,6 +28,6 @@
</head>
<body>
<div id="root"></div>
<script src="/client/coral-docs/bundle.js" charset="utf-8"></script>
<script src="<%= BASE_PATH %>client/coral-docs/bundle.js" charset="utf-8"></script>
</body>
</html>
+1 -1
View File
@@ -117,7 +117,7 @@
}
$.ajax({
url: '/api/v1/account/password/reset',
url: '<%= BASE_PATH %>api/v1/account/password/reset',
contentType: 'application/json',
method: 'PUT',
data: JSON.stringify({password: password, token: location.hash.replace('#', '')})
+1 -2
View File
@@ -17,7 +17,6 @@
}
</style>
<title><%= title %></title>
<base href="<%= BASE_URL %>"/>
</head>
<body>
<main>
@@ -25,7 +24,7 @@
<p><%= body %></p>
<p><a href="admin">Admin</a> - <a href="assets">All Assets</a></p>
<div id='coralStreamEmbed'></div>
<script src="embed.js" async onload="
<script src="<%= BASE_URL %>embed.js" async onload="
window.TalkEmbed = Coral.Talk.render(document.getElementById('coralStreamEmbed'), {
talk: '<%= BASE_URL %>',
asset_url: '<%= asset_url ? asset_url : '' %>',
+1 -1
View File
@@ -4,7 +4,7 @@
Asset list
</h1>
<% assets.forEach(function (asset) { %>
<a href="<%= BASE_URL %>assets/id/<%= asset.id %>"><%= asset.url %></a><br />
<a href="<%= BASE_PATH %>assets/id/<%= asset.id %>"><%= asset.url %></a><br />
<% }) %>
<p>
(For dev use only. FYI, you can: ?skip=100&limit=25)
+1 -1
View File
@@ -17,6 +17,6 @@
<body>
<div id="talk-embed-stream-container"></div>
<script src="<%= BASE_URL %>client/embed/stream/bundle.js"></script>
<script src="<%= BASE_PATH %>client/embed/stream/bundle.js"></script>
</body>
</html>