mirror of
https://github.com/wassname/talk.git
synced 2026-07-03 16:25:13 +08:00
Merge branch 'master' of https://github.com/coralproject/talk into reader-flags
This commit is contained in:
@@ -55,6 +55,7 @@
|
||||
"no-var": [2],
|
||||
"no-lonely-if": [2],
|
||||
"curly": [2],
|
||||
"no-unused-vars": ["error", { "argsIgnorePattern": "next" }],
|
||||
"no-multiple-empty-lines": [
|
||||
"error",
|
||||
{"max": 1}
|
||||
|
||||
+1
-7
@@ -11,15 +11,9 @@ EXPOSE 5000
|
||||
|
||||
# Install app dependencies
|
||||
COPY package.json /usr/src/app/
|
||||
RUN npm install
|
||||
RUN npm install --production
|
||||
|
||||
# Bundle app source
|
||||
COPY . /usr/src/app
|
||||
|
||||
# Compile static assets
|
||||
RUN npm run build
|
||||
|
||||
# Prune development dependancies
|
||||
RUN npm prune --production
|
||||
|
||||
CMD [ "npm", "start" ]
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
# Talk [](https://circleci.com/gh/coralproject/talk)
|
||||
A commenting platform from The Coral Project. [https://coralproject.net](https://coralproject.net)
|
||||
|
||||
## Contributing to Talk
|
||||
|
||||
### Local Dependencies
|
||||
Node
|
||||
Mongo
|
||||
|
||||
### Getting Started
|
||||
`npm install`
|
||||
Run it once to install the dependencies.
|
||||
@@ -8,16 +14,22 @@ Run it once to install the dependencies.
|
||||
`npm start`
|
||||
Runs Talk.
|
||||
|
||||
### Running with Docker
|
||||
Make sure you have Docker running first and then run `docker-compose up -d`
|
||||
|
||||
### Testing
|
||||
`npm test`
|
||||
|
||||
### Lint
|
||||
`npm run lint`
|
||||
|
||||
### Helpful URLs
|
||||
Bare comment stream: http://localhost:5000/client/coral-embed-stream/
|
||||
Comment stream embedded on sample article: http://localhost:5000/client/coral-embed-stream/samplearticle.html
|
||||
Moderator view: http://localhost:5000/admin/
|
||||
|
||||
### Docs
|
||||
`swagger.yaml`
|
||||
|
||||
### Mantainers
|
||||
|
||||
### License
|
||||
**Apache-2.0**
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# Product's Terminology
|
||||
|
||||
This is a guide to have a common language to talk about "Talk".
|
||||
|
||||
## Definitions
|
||||
|
||||
* Site - a top level site, aka nytimes.com
|
||||
* Section - the section of a site, aka, Politics.
|
||||
* Subsection - the section of a site, aka, Politics.
|
||||
* Asset - An article/video/etc identified by URL.
|
||||
|
||||
* Embed - Things we put on a asset: comment box, ToS, Stream, etc…
|
||||
* Stream - All the activity on a certain asset. Container for Comments, actions, user
|
||||
* Thread - defined by a parent and everything below. All replies to a comment and their replies, etc…
|
||||
* Comment - a kind of user-generated content submitted by a comment author
|
||||
* A parent comment has replies to it
|
||||
* A child comments is a reply to another comment
|
||||
* A comment can be both a parent comment and a child of another comment
|
||||
* A top-level comment is a comment that is not a reply to any other comment
|
||||
* A nth-level comment refers to the number of replies away from the top-level comment
|
||||
|
||||
* User - an item to represent a person using Talk. It could be a moderator, reader, etc.
|
||||
* User Roles:
|
||||
* Active: some who takes action (logged in or not)
|
||||
* Passive: some who just reads, no actions performed
|
||||
* Comment Author: The user who wrote the comment
|
||||
* Staff Member: someone who works for an organization (tagged for leverage in trust)
|
||||
* Moderator: someone with the ability to access the moderation queue and perform moderation actions
|
||||
* Administrator: has the ability to change the setup of their coral space
|
||||
* Public Profile: information about users shown in public
|
||||
* Private Profile: information about users shown only to user about themselves
|
||||
* Protected Profile: information about users that only moderators and admins can see
|
||||
|
||||
* Queue - Group of items based on a query, aka - moderation queue
|
||||
* Target - The item/s on which an action is performed..
|
||||
|
||||
## Actions
|
||||
|
||||
Actions are performed by users on items. Actions themselves are items. This requires two relationships: action on item, and user performs action.
|
||||
|
||||
### Flag
|
||||
* A Flagger(user) performs a Flag
|
||||
* A Flag is performed on a Comment or a username or profile content
|
||||
|
||||
|
||||
## Moderation Actions and Status
|
||||
|
||||
Comments contain a field `status`. As moderation actions are peformed, the status changes.
|
||||
|
||||
* Initial status is empty.
|
||||
* When a moderator Approves, the status is set to 'approved'.
|
||||
* When a moderator Rejects, the status is set to 'reject'.
|
||||
|
||||
### Pre and post moderation
|
||||
|
||||
Comments can be set to be premoderated or postmoderated.
|
||||
|
||||
Premoderation means that moderation has to occur _before_ a comment is shown on the site:
|
||||
|
||||
* New comments are shown in the moderator queues immediately.
|
||||
* The are not shown to users until (aka in streams) until they are approved by a moderator.
|
||||
|
||||
Postmoderation means that comments appear on the site _before_ any moderation action is taken.
|
||||
|
||||
* New comments appear in comment streams immediately.
|
||||
* New comments do not appear in moderation queues unless they are flagged by other users.
|
||||
@@ -8,11 +8,60 @@ const app = express();
|
||||
// Middleware declarations.
|
||||
app.use(morgan('dev'));
|
||||
app.use(bodyParser.json());
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
app.set('view engine', 'ejs');
|
||||
|
||||
// API Routes.
|
||||
// Routes.
|
||||
app.use('/api/v1', require('./routes/api'));
|
||||
app.use('/client', express.static(path.join(__dirname, 'dist')));
|
||||
app.use('/admin', require('./routes/admin'));
|
||||
|
||||
// Static Routes.
|
||||
app.use('/client/', express.static(path.join(__dirname, 'dist')));
|
||||
//==============================================================================
|
||||
// ERROR HANDLING
|
||||
//==============================================================================
|
||||
|
||||
const ErrNotFound = new Error('Not Found');
|
||||
ErrNotFound.status = 404;
|
||||
|
||||
// Catch 404 and forward to error handler.
|
||||
app.use((req, res, next) => {
|
||||
next(ErrNotFound);
|
||||
});
|
||||
|
||||
// General error handler. Respond with the message and error if we have it while
|
||||
// returning a status code that makes sense.
|
||||
if (app.get('env') === 'development') {
|
||||
app.use('/api', (err, req, res, next) => {
|
||||
res.status(err.status || 500);
|
||||
res.json({
|
||||
message: err.message,
|
||||
error: err
|
||||
});
|
||||
});
|
||||
|
||||
app.use('/', (err, req, res, next) => {
|
||||
res.status(err.status || 500);
|
||||
res.render('error', {
|
||||
message: err.message,
|
||||
error: err
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
app.use('/api', (err, req, res, next) => {
|
||||
res.status(err.status || 500);
|
||||
res.json({
|
||||
message: err.message,
|
||||
error: {}
|
||||
});
|
||||
});
|
||||
|
||||
app.use('/', (err, req, res, next) => {
|
||||
res.status(err.status || 500);
|
||||
res.render('error', {
|
||||
message: err.message,
|
||||
error: {}
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
|
||||
@@ -8,6 +8,7 @@ test:
|
||||
override:
|
||||
- MOCHA_FILE=$CIRCLE_TEST_REPORTS/junit/test-results.xml ./node_modules/.bin/mocha tests --reporter mocha-junit-reporter
|
||||
- npm run lint
|
||||
- npm run build
|
||||
|
||||
deployment:
|
||||
release:
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
node_modules
|
||||
public/bundle.js
|
||||
public/embed/comment-stream
|
||||
.DS_Store
|
||||
npm-debug.log
|
||||
config.json
|
||||
yarn.lock
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"basePath": "admin"
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"basePath": "client/coral-admin"
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"name": "coral-admin",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "./node_modules/.bin/webpack --config webpack.config.js",
|
||||
"start": "./node_modules/.bin/webpack-dev-server --config webpack.config.dev.js --inline --hot --content-base public --port 3142"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"hammerjs": "2.0.8",
|
||||
"immutable": "3.8.1",
|
||||
"keymaster": "1.6.2",
|
||||
"material-design-lite": "1.2.1",
|
||||
"react": "^15.3.2",
|
||||
"react-dom": "^15.3.2",
|
||||
"react-mdl": "^1.7.2",
|
||||
"react-redux": "^4.4.5",
|
||||
"react-router": "^3.0.0",
|
||||
"redux": "3.6.0",
|
||||
"redux-thunk": "2.1.0",
|
||||
"timeago.js": "2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "6.5.0",
|
||||
"babel-core": "^6.18.2",
|
||||
"babel-loader": "^6.2.7",
|
||||
"copy-webpack-plugin": "3.0.1",
|
||||
"css-loader": "0.25.0",
|
||||
"json-loader": "0.5.4",
|
||||
"postcss-loader": "0.13.0",
|
||||
"postcss-modules": "0.5.2",
|
||||
"precss": "1.4.0",
|
||||
"standard": "8.2.0",
|
||||
"style-loader": "0.13.1",
|
||||
"webpack": "^1.13.3",
|
||||
"webpack-dev-server": "1.16.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from 'react'
|
||||
import { Router, Route, Redirect, IndexRoute, browserHistory } from 'react-router'
|
||||
|
||||
import ModerationQueue from 'containers/ModerationQueue'
|
||||
import CommentStream from 'containers/CommentStream'
|
||||
import EmbedLink from 'components/EmbedLink'
|
||||
import Configure from 'containers/Configure'
|
||||
import CommunityContainer from 'containers/CommunityContainer'
|
||||
import LayoutContainer from 'containers/LayoutContainer'
|
||||
|
||||
const routes = (
|
||||
<Route path='admin' component={LayoutContainer}>
|
||||
<IndexRoute component={ModerationQueue} />
|
||||
<Route path='embed' component={CommentStream} />
|
||||
<Route path='embedlink' component={EmbedLink} />
|
||||
<Route path='community' component={CommunityContainer} />
|
||||
<Route path='configure' component={Configure} />
|
||||
</Route>
|
||||
)
|
||||
|
||||
const AppRouter = () => <Router history={browserHistory} routes={routes} />
|
||||
|
||||
export default AppRouter
|
||||
@@ -1,25 +1,16 @@
|
||||
|
||||
import React from 'react'
|
||||
import { Provider } from 'react-redux'
|
||||
import 'material-design-lite'
|
||||
import { Router, Route, browserHistory } from 'react-router'
|
||||
import ModerationQueue from 'containers/ModerationQueue'
|
||||
import { Layout } from 'react-mdl'
|
||||
import store from 'services/store'
|
||||
import CommentStream from 'containers/CommentStream'
|
||||
import EmbedLink from 'components/EmbedLink'
|
||||
import Configure from 'containers/Configure'
|
||||
import config from 'services/config'
|
||||
|
||||
import AppRouter from '../AppRouter'
|
||||
|
||||
export default class App extends React.Component {
|
||||
render (props) {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<Router history={browserHistory}>
|
||||
<Route path={config.basePath} component={ModerationQueue} />
|
||||
<Route path={`${config.basePath}/embed`} component={CommentStream} />
|
||||
<Route path={`${config.basePath}/embedlink`} component={EmbedLink} />
|
||||
<Route path={`${config.basePath}/configure`} component={Configure} />
|
||||
</Router>
|
||||
<AppRouter store={store} />
|
||||
</Provider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import React from 'react'
|
||||
import { Layout, Navigation, Drawer, Header } from 'react-mdl'
|
||||
import { Link } from 'react-router'
|
||||
import styles from './Header.css'
|
||||
import config from 'services/config'
|
||||
|
||||
// App header. If we add a navbar it should be here
|
||||
export default (props) => (
|
||||
<Layout fixedDrawer>
|
||||
<Header title='Talk'>
|
||||
<Navigation>
|
||||
<Link className={styles.navLink} to={`/${config.basePath}/`}>Moderate</Link>
|
||||
<Link className={styles.navLink} to={`/${config.basePath}/configure`}>Configure</Link>
|
||||
</Navigation>
|
||||
</Header>
|
||||
<Drawer>
|
||||
<Navigation>
|
||||
<Link className={styles.navLink} to={`/${config.basePath}/`}>Moderate</Link>
|
||||
<Link className={styles.navLink} to={`/${config.basePath}/configure`}>Configure</Link>
|
||||
</Navigation>
|
||||
</Drawer>
|
||||
{props.children}
|
||||
</Layout>
|
||||
)
|
||||
@@ -1,12 +0,0 @@
|
||||
import React from 'react'
|
||||
import {Layout} from 'react-mdl'
|
||||
import 'material-design-lite'
|
||||
import Header from 'components/Header'
|
||||
|
||||
export default (props) => (
|
||||
<Layout>
|
||||
<Header>
|
||||
{props.children}
|
||||
</Header>
|
||||
</Layout>
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
import React from 'react'
|
||||
import { Navigation, Drawer } from 'react-mdl'
|
||||
import { Link } from 'react-router'
|
||||
import styles from './Header.css'
|
||||
|
||||
export default () => (
|
||||
<Drawer>
|
||||
<Navigation>
|
||||
<Link className={styles.navLink} to="/admin">Moderate</Link>
|
||||
<Link className={styles.navLink} to="/admin/community">Community</Link>
|
||||
<Link className={styles.navLink} to="/admin/configure">Configure</Link>
|
||||
</Navigation>
|
||||
</Drawer>
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
import React from 'react'
|
||||
import { Navigation, Header } from 'react-mdl'
|
||||
import { Link } from 'react-router'
|
||||
import styles from './Header.css'
|
||||
|
||||
export default () => (
|
||||
<Header title='Talk'>
|
||||
<Navigation>
|
||||
<Link className={styles.navLink} to="/admin">Moderate</Link>
|
||||
<Link className={styles.navLink} to="/admin/community">Community</Link>
|
||||
<Link className={styles.navLink} to="/admin/configure">Configure</Link>
|
||||
</Navigation>
|
||||
</Header>
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
import React from 'react'
|
||||
import { Layout as LayoutMDL} from 'react-mdl'
|
||||
import Header from './Header'
|
||||
import Drawer from './Drawer'
|
||||
|
||||
export const Layout = ({ children }) => (
|
||||
<LayoutMDL fixedDrawer>
|
||||
<Header />
|
||||
<Drawer />
|
||||
{children}
|
||||
</LayoutMDL>
|
||||
)
|
||||
@@ -6,7 +6,6 @@ import { connect } from 'react-redux'
|
||||
import { createComment, flagComment } from 'actions/comments'
|
||||
import CommentList from 'components/CommentList'
|
||||
import CommentBox from 'components/CommentBox'
|
||||
import Page from 'components/Page'
|
||||
|
||||
/**
|
||||
* Renders a comment stream using a CommentList component
|
||||
@@ -44,19 +43,17 @@ class CommentStream extends React.Component {
|
||||
// Render the comment box along with the CommentList
|
||||
render ({ comments }, { snackbar, snackbarMsg }) {
|
||||
return (
|
||||
<Page>
|
||||
<div className={styles.container}>
|
||||
<CommentBox onSubmit={this.onSubmit} />
|
||||
<CommentList isActive hideActive
|
||||
singleView={false}
|
||||
commentIds={comments.get('ids')}
|
||||
comments={comments.get('byId')}
|
||||
onClickAction={this.onClickAction}
|
||||
actions={['flag']}
|
||||
loading={comments.loading} />
|
||||
<Snackbar active={snackbar}>{snackbarMsg}</Snackbar>
|
||||
</div>
|
||||
</Page>
|
||||
<div className={styles.container}>
|
||||
<CommentBox onSubmit={this.onSubmit} />
|
||||
<CommentList isActive hideActive
|
||||
singleView={false}
|
||||
commentIds={comments.get('ids')}
|
||||
comments={comments.get('byId')}
|
||||
onClickAction={this.onClickAction}
|
||||
actions={['flag']}
|
||||
loading={comments.loading} />
|
||||
<Snackbar active={snackbar}>{snackbarMsg}</Snackbar>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import I18n from 'coral-framework/i18n/i18n'
|
||||
import translations from '../translations'
|
||||
|
||||
export default class CommunityContainer extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Community</h1>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,9 @@ import {
|
||||
Button,
|
||||
Icon
|
||||
} from 'react-mdl'
|
||||
import Page from 'components/Page'
|
||||
import styles from './Configure.css'
|
||||
import I18n from 'coral-framework/i18n/i18n'
|
||||
import translations from '../translations'
|
||||
|
||||
class Configure extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -40,12 +41,28 @@ class Configure extends React.Component {
|
||||
</List>
|
||||
}
|
||||
|
||||
copyToClipBoard (event) {
|
||||
const copyTextarea = document.querySelector('.' + styles.embedInput)
|
||||
copyTextarea.select()
|
||||
|
||||
try {
|
||||
document.execCommand('copy')
|
||||
} catch (err) {
|
||||
console.error('Unable to copy')
|
||||
}
|
||||
}
|
||||
|
||||
getEmbed () {
|
||||
const embedText =
|
||||
`<div id='coralStreamEmbed'></div><script type='text/javascript' src='https://pym.nprapps.org/pym.v1.min.js'></script><script>var pymParent = new pym.Parent('coralStreamEmbed', '${window.location.protocol}//${window.location.host}/client/coral-embed-stream/', {title: 'comments'});</script>`
|
||||
|
||||
return <List>
|
||||
<ListItem className={styles.configSettingEmbed}>
|
||||
<p>Copy and paste code below into your CMS to embed your comment box in your articles</p>
|
||||
<input type='text' className={styles.embedInput} />
|
||||
<Button raised colored>Copy</Button>
|
||||
<textarea type='text' className={styles.embedInput}>
|
||||
{embedText}
|
||||
</textarea>
|
||||
<Button raised colored>{lang.t('embedlink.copy')}</Button>
|
||||
</ListItem>
|
||||
</List>
|
||||
}
|
||||
@@ -60,7 +77,6 @@ class Configure extends React.Component {
|
||||
: 'Embed Comment Stream'
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<div className={styles.container}>
|
||||
<div className={styles.leftColumn}>
|
||||
<List>
|
||||
@@ -88,9 +104,10 @@ class Configure extends React.Component {
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(x => x)(Configure)
|
||||
|
||||
const lang = new I18n(translations)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import React, { Component }from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import { Layout } from '../components/ui/Layout'
|
||||
|
||||
class LayoutContainer extends Component {
|
||||
render () {
|
||||
return <Layout { ...this.props } />
|
||||
}
|
||||
}
|
||||
|
||||
LayoutContainer.propTypes = {}
|
||||
|
||||
const mapStateToProps = state => ({ data: {} })
|
||||
|
||||
const mapDispatchToProps = (dispatch, ownProps) => ({ dispatch })
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(LayoutContainer)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import ModerationKeysModal from 'components/ModerationKeysModal'
|
||||
import Page from 'components/Page'
|
||||
import CommentList from 'components/CommentList'
|
||||
import { updateStatus } from 'actions/comments'
|
||||
import styles from './ModerationQueue.css'
|
||||
@@ -20,8 +19,6 @@ class ModerationQueue extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
console.log('ModerationQueue', props)
|
||||
|
||||
this.state = { activeTab: 'pending', singleView: false, modalOpen: false }
|
||||
}
|
||||
|
||||
@@ -60,10 +57,9 @@ class ModerationQueue extends React.Component {
|
||||
render () {
|
||||
const { comments } = this.props
|
||||
const { activeTab, singleView, modalOpen } = this.state
|
||||
console.log('moderation queue', styles)
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<div>
|
||||
<div className='mdl-tabs mdl-js-tabs mdl-js-ripple-effect'>
|
||||
<div className='mdl-tabs__tab-bar'>
|
||||
<a href='#pending' onClick={() => this.onTabClick('pending')}
|
||||
@@ -109,7 +105,7 @@ class ModerationQueue extends React.Component {
|
||||
<ModerationKeysModal open={modalOpen}
|
||||
onClose={() => this.setState({ modalOpen: false })} />
|
||||
</div>
|
||||
</Page>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const autoprefixer = require('autoprefixer')
|
||||
const precss = require('precss')
|
||||
const config = require('./config.json')
|
||||
|
||||
// doing a string replace here because I spent a day trying to do it the "webpack" way
|
||||
// ond nothing works. just trying to replace a string in an index.html file is
|
||||
// apparently something no one has ever done in the js community.
|
||||
let templateString = fs.readFileSync(path.join(__dirname, 'index.ejs')).toString()
|
||||
templateString = templateString.replace('<%= basePath %>', config.basePath)
|
||||
fs.writeFileSync(path.join(__dirname, 'public/index.html'), templateString)
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
'bundle': path.join(__dirname, 'src', 'index')
|
||||
},
|
||||
output: {
|
||||
path: path.join(__dirname, '..', '..', 'dist', 'coral-admin'),
|
||||
filename: '[name].js'
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{ test: /.js$/, loader: 'babel', include: path.join(__dirname, 'src'), exclude: /node_modules/ },
|
||||
{ test: /\.json$/, loaders: 'json', include: __dirname, exclude: /node_modules/ },
|
||||
{ test: /.css$/, loaders: ['style-loader', 'css-loader?importLoaders=1', 'postcss-loader'] }
|
||||
]
|
||||
},
|
||||
plugins: [ autoprefixer, precss ],
|
||||
resolve: {
|
||||
root: [
|
||||
path.resolve('./src'),
|
||||
path.resolve('../')
|
||||
]
|
||||
},
|
||||
devServer: {
|
||||
historyApiFallback: {
|
||||
index: '/'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const devConfig = require('./webpack.config.dev')
|
||||
const autoprefixer = require('autoprefixer')
|
||||
const precss = require('precss')
|
||||
const Copy = require('copy-webpack-plugin')
|
||||
const config = require('./config.json')
|
||||
|
||||
// doing a string replace here because I spent a day trying to do it the "webpack" way
|
||||
// ond nothing works. just trying to replace a string in an index.html file is
|
||||
// apparently something no one has ever done in the js community.
|
||||
let templateString = fs.readFileSync(path.join(__dirname, 'index.ejs')).toString()
|
||||
templateString = templateString.replace('<%= basePath %>', config.basePath)
|
||||
fs.writeFileSync(path.join(__dirname, 'public/index.html'), templateString)
|
||||
|
||||
module.exports = Object.assign({}, devConfig, {
|
||||
module: {
|
||||
context: __dirname,
|
||||
loaders: [
|
||||
{ test: /.js$/, loader: 'babel', include: [path.join(__dirname, 'src'), path.join(__dirname, '../', 'coral-framework')], exclude: /node_modules/ },
|
||||
{ test: /.json$/, loader: 'json', include: __dirname, exclude: /node_modules/ },
|
||||
{ test: /.css$/, loaders: ['style-loader', 'css-loader?importLoaders=1', 'postcss-loader'] }
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new Copy([{
|
||||
from: path.join(__dirname, '..', 'coral-embed-stream', 'dist'),
|
||||
to: './embed/comment-stream'
|
||||
}]),
|
||||
autoprefixer, precss
|
||||
]
|
||||
})
|
||||
@@ -1,41 +0,0 @@
|
||||
var path = require('path')
|
||||
var express = require('express')
|
||||
var http = require('http')
|
||||
var webpack = require('webpack')
|
||||
var config = require('./webpack.config.dev')
|
||||
var Dashboard = require('webpack-dashboard')
|
||||
var DashboardPlugin = require('webpack-dashboard/plugin')
|
||||
|
||||
var app = express()
|
||||
var server = http.Server(app)
|
||||
|
||||
var compiler = webpack(config)
|
||||
var dashboard = new Dashboard()
|
||||
compiler.apply(new DashboardPlugin(dashboard.setData))
|
||||
|
||||
app.use(express.static('public'))
|
||||
|
||||
app.use(require('webpack-dev-middleware')(compiler, {
|
||||
noInfo: true,
|
||||
quiet: true,
|
||||
publicPath: config.output.publicPath
|
||||
}))
|
||||
|
||||
app.use(require('webpack-hot-middleware')(compiler, {log: () => {}}))
|
||||
|
||||
app.get('/default.css', function (req, res) {
|
||||
res.sendFile(path.join(__dirname, '/style/default.css'))
|
||||
})
|
||||
|
||||
app.get('*', function (req, res) {
|
||||
res.sendFile(path.join(__dirname, 'index.html'))
|
||||
})
|
||||
|
||||
server.listen(6182, 'localhost', function (err) {
|
||||
if (err) {
|
||||
console.log(err)
|
||||
return
|
||||
}
|
||||
|
||||
console.log('Listening at http://localhost:6182')
|
||||
})
|
||||
@@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="default.css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Lato|Open+Sans" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons"
|
||||
rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div id="coralStream"/>
|
||||
<script src="./bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,70 +0,0 @@
|
||||
var path = require('path')
|
||||
var webpack = require('webpack')
|
||||
const Copy = require('copy-webpack-plugin')
|
||||
|
||||
module.exports = {
|
||||
devtool: 'eval',
|
||||
entry: [
|
||||
'babel-polyfill',
|
||||
'webpack-hot-middleware/client',
|
||||
path.join(__dirname, 'src', 'app')
|
||||
],
|
||||
output: {
|
||||
path: path.join(__dirname, '..', '..','dist', 'coral-embed-stream'),
|
||||
filename: 'bundle.js',
|
||||
publicPath: '/'
|
||||
},
|
||||
resolve: {
|
||||
root: [
|
||||
path.resolve(__dirname, 'src'),
|
||||
path.resolve(__dirname, '..')
|
||||
],
|
||||
extensions: ['', '.js', '.jsx']
|
||||
},
|
||||
plugins: [
|
||||
new Copy([{
|
||||
from: path.join(__dirname, 'index.html')
|
||||
},
|
||||
{
|
||||
from: path.join(__dirname, 'style', 'default.css')
|
||||
},
|
||||
{
|
||||
from: path.join(__dirname, 'public'),
|
||||
to: './'
|
||||
}]),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
'NODE_ENV': JSON.stringify('development')
|
||||
}
|
||||
}),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.ProvidePlugin({
|
||||
'fetch': 'imports?this=>global!exports?global.fetch!whatwg-fetch'
|
||||
}),
|
||||
new webpack.NoErrorsPlugin()
|
||||
],
|
||||
module: {
|
||||
loaders: [{
|
||||
test: /\.(js|jsx)$/,
|
||||
|
||||
loaders: ['babel'],
|
||||
exclude: /node_modules/,
|
||||
include: path.join(__dirname, '../')
|
||||
}, {
|
||||
test: /\.css$/,
|
||||
loader: 'style-loader!css-loader'
|
||||
}, {
|
||||
test: /\.png$/,
|
||||
loader: 'url-loader?limit=100000'
|
||||
}, {
|
||||
test: /\.(jpg|png|gif|svg)$/,
|
||||
loader: 'file-loader'
|
||||
}, {
|
||||
test: /\.json$/,
|
||||
loader: 'json-loader'
|
||||
}, {
|
||||
test: /\.woff$/,
|
||||
loader: 'url?limit=100000'
|
||||
}]
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
const Copy = require('copy-webpack-plugin')
|
||||
|
||||
//Keeping this file for reference, it should move to a global webpack.
|
||||
|
||||
module.exports = {
|
||||
devtool: 'source-map',
|
||||
entry: [
|
||||
'babel-polyfill',
|
||||
path.join(__dirname, 'src', 'app')
|
||||
],
|
||||
output: {
|
||||
path: path.join(__dirname, '..', '..','dist', 'coral-embed-stream'),
|
||||
filename: 'bundle.js',
|
||||
publicPath: '/dist/'
|
||||
},
|
||||
resolve: {
|
||||
root: [
|
||||
path.resolve(__dirname, 'src')
|
||||
],
|
||||
extensions: ['', '.js', '.jsx']
|
||||
},
|
||||
plugins: [
|
||||
new Copy([{
|
||||
from: path.join(__dirname, 'index.html')
|
||||
},
|
||||
{
|
||||
from: path.join(__dirname, 'style', 'default.css')
|
||||
},
|
||||
{
|
||||
from: path.join(__dirname, 'public'),
|
||||
to: './'
|
||||
},
|
||||
{
|
||||
from: path.resolve(__dirname, '..', 'coral-framework', 'i18n', 'translations'),
|
||||
to: './translations'
|
||||
}]),
|
||||
new webpack.optimize.OccurenceOrderPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
'NODE_ENV': JSON.stringify('production')
|
||||
}
|
||||
}),
|
||||
new webpack.ProvidePlugin({
|
||||
'fetch': 'imports?this=>global!exports?global.fetch!whatwg-fetch'
|
||||
}),
|
||||
new webpack.ExtendedAPIPlugin()
|
||||
],
|
||||
module: {
|
||||
loaders: [{
|
||||
test: /\.(js|jsx)$/,
|
||||
loaders: ['babel'],
|
||||
exclude: /node_modules/,
|
||||
include: path.join(__dirname, '../')
|
||||
}, {
|
||||
test: /\.css$/,
|
||||
loader: 'style-loader!css-loader'
|
||||
}, {
|
||||
test: /\.png$/,
|
||||
loader: 'url-loader?limit=100000'
|
||||
}, {
|
||||
test: /\.jpg$/,
|
||||
loader: 'file-loader'
|
||||
}, {
|
||||
test: /\.json$/,
|
||||
loader: 'json-loader'
|
||||
}, {
|
||||
test: /\.woff$/,
|
||||
loader: 'url?limit=100000'
|
||||
}]
|
||||
}
|
||||
}
|
||||
Vendored
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"short_name": "Talk",
|
||||
"name": "Talk",
|
||||
"icons": [
|
||||
{
|
||||
"src": "https://coralproject.net/images/icon-coral-white.svg",
|
||||
"sizes": "150x150"
|
||||
}
|
||||
],
|
||||
"start_url": "./",
|
||||
"display": "standalone"
|
||||
}
|
||||
@@ -68,6 +68,32 @@ ActionSchema.statics.getActionSummaries = function(item_ids) {
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Finds all comments for a specific action.
|
||||
* @param {String} action_type type of action
|
||||
* @param {String} item_type type of item the action is on
|
||||
*/
|
||||
ActionSchema.statics.findByType = function(action_type, item_type) {
|
||||
return Action.find({
|
||||
'action_type': action_type,
|
||||
'item_type': item_type
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds all comments ids for a specific action.
|
||||
* @param {String} action_type type of action
|
||||
* @param {String} item_type type of item the action is on
|
||||
*/
|
||||
ActionSchema.statics.findCommentsIdByActionType = function(action_type, item_type) {
|
||||
return Action.find({
|
||||
'action_type': action_type,
|
||||
'item_type': item_type
|
||||
},
|
||||
'item_id'
|
||||
);
|
||||
};
|
||||
|
||||
const Action = mongoose.model('Action', ActionSchema);
|
||||
|
||||
module.exports = Action;
|
||||
|
||||
@@ -31,6 +31,23 @@ const CommentSchema = new Schema({
|
||||
}
|
||||
});
|
||||
|
||||
//==============================================================================
|
||||
// New Statics
|
||||
//==============================================================================
|
||||
|
||||
/**
|
||||
* Create a comment.
|
||||
* @param {String} body content of comment
|
||||
*/
|
||||
CommentSchema.statics.new = function(body, author_id, asset_id, parent_id, status, username) {
|
||||
let comment = new Comment({body, author_id, asset_id, parent_id, status, username});
|
||||
return comment.save();
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// Find Statics
|
||||
//==============================================================================
|
||||
|
||||
/**
|
||||
* Finds a comment by the id.
|
||||
* @param {String} id identifier of comment (uuid)
|
||||
@@ -47,6 +64,28 @@ CommentSchema.statics.findByAssetId = function(asset_id) {
|
||||
return Comment.find({asset_id});
|
||||
};
|
||||
|
||||
/**
|
||||
* Find comments by an action that was performed on them.
|
||||
* @param {String} action_type the type of action that was performed on the comment
|
||||
*/
|
||||
CommentSchema.statics.findByActionType = function(action_type) {
|
||||
return Action.findCommentsIdByActionType(action_type, 'comment').then((actions) => {
|
||||
return Comment.find({'id': {'$in': actions.map(function(a){return a.item_id;})}});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Find comments by their status.
|
||||
* @param {String} status the status to search for
|
||||
*/
|
||||
CommentSchema.statics.findByStatus = function(status) {
|
||||
return Comment.find({'status': status});
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// Update Statics
|
||||
//==============================================================================
|
||||
|
||||
/**
|
||||
* Change the status of a comment.
|
||||
* @param {String} id identifier of the comment (uuid)
|
||||
@@ -72,6 +111,19 @@ CommentSchema.statics.addAction = function(id, user_id, action_type) {
|
||||
return action.save();
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// Remove Statics
|
||||
//==============================================================================
|
||||
|
||||
/**
|
||||
* Change the status of a comment.
|
||||
* @param {String} id identifier of the comment (uuid)
|
||||
* @param {String} status the new status of the comment
|
||||
*/
|
||||
CommentSchema.statics.removeById = function(id) {
|
||||
return Comment.remove({'id': id});
|
||||
};
|
||||
|
||||
const Comment = mongoose.model('Comment', CommentSchema);
|
||||
|
||||
module.exports = Comment;
|
||||
|
||||
+25
-16
@@ -5,12 +5,13 @@
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"start": "./bin/www",
|
||||
"build": "./node_modules/webpack/bin/webpack.js --config ./client/coral-embed-stream/webpack.config.js && cd client/coral-admin && npm run build",
|
||||
"build": "webpack --config webpack.config.js --bail",
|
||||
"build-watch": "webpack --config webpack.config.dev.js --watch",
|
||||
"lint": "eslint .",
|
||||
"pretest": "npm install",
|
||||
"test": "mocha tests --recursive",
|
||||
"test-watch": "mocha tests --recursive -w",
|
||||
"embed-start": "./node_modules/webpack/bin/webpack.js --config ./client/coral-embed-stream/webpack.config.dev.js && ./bin/www"
|
||||
"embed-start": "npm run build && ./bin/www"
|
||||
},
|
||||
"config": {
|
||||
"pre-git": {
|
||||
@@ -44,48 +45,56 @@
|
||||
"dependencies": {
|
||||
"body-parser": "^1.15.2",
|
||||
"debug": "^2.2.0",
|
||||
"ejs": "^2.5.2",
|
||||
"express": "^4.14.0",
|
||||
"mongoose": "^4.6.5",
|
||||
"uuid": "^2.0.3",
|
||||
"morgan": "^1.7.0"
|
||||
"morgan": "^1.7.0",
|
||||
"uuid": "^2.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "6.14.0",
|
||||
"babel-jest": "^15.0.0",
|
||||
"babel-loader": "6.2.5",
|
||||
"babel-plugin-transform-async-to-generator": "^6.8.0",
|
||||
"autoprefixer": "^6.5.0",
|
||||
"babel-core": "^6.18.2",
|
||||
"babel-loader": "^6.2.7",
|
||||
"babel-plugin-transform-async-to-generator": "^6.16.0",
|
||||
"babel-plugin-transform-decorators-legacy": "^1.3.4",
|
||||
"babel-plugin-transform-object-assign": "^6.8.0",
|
||||
"babel-plugin-transform-react-jsx": "^6.8.0",
|
||||
"babel-polyfill": "^6.13.0",
|
||||
"babel-preset-es2015": "6.13.0",
|
||||
"babel-polyfill": "^6.16.0",
|
||||
"babel-preset-es2015": "^6.18.0",
|
||||
"babel-preset-es2015-minimal": "^2.1.0",
|
||||
"babel-preset-stage-0": "^6.16.0",
|
||||
"chai": "^3.5.0",
|
||||
"chai-http": "^3.0.0",
|
||||
"copy-webpack-plugin": "^3.0.1",
|
||||
"copy-webpack-plugin": "^4.0.0",
|
||||
"css-loader": "^0.25.0",
|
||||
"eslint": "^3.9.1",
|
||||
"exports-loader": "^0.6.3",
|
||||
"hammerjs": "^2.0.8",
|
||||
"immutable": "^3.8.1",
|
||||
"imports-loader": "^0.6.5",
|
||||
"json-loader": "^0.5.4",
|
||||
"keymaster": "^1.6.2",
|
||||
"material-design-lite": "^1.2.1",
|
||||
"mocha": "^3.1.2",
|
||||
"mocha-junit-reporter": "^1.12.1",
|
||||
"postcss-loader": "^1.1.0",
|
||||
"postcss-modules": "^0.5.2",
|
||||
"postcss-smart-import": "^0.5.1",
|
||||
"pre-git": "^3.10.0",
|
||||
"precss": "^1.4.0",
|
||||
"pym.js": "^1.1.1",
|
||||
"react": "15.3.2",
|
||||
"react-dom": "15.3.2",
|
||||
"react-mdl": "^1.7.2",
|
||||
"react-redux": "^4.4.5",
|
||||
"react-router": "^3.0.0",
|
||||
"redux": "^3.6.0",
|
||||
"redux-thunk": "^2.1.0",
|
||||
"regenerator": "^0.8.46",
|
||||
"style-loader": "^0.13.1",
|
||||
"supertest": "^2.0.1",
|
||||
"timeago.js": "^2.0.3",
|
||||
"webpack": "^1.13.2",
|
||||
"webpack-dashboard": "^0.2.0",
|
||||
"webpack-dev-middleware": "^1.8.3",
|
||||
"webpack-hot-middleware": "^2.12.2",
|
||||
"webpack-module-hot-accept": "^1.0.4",
|
||||
"webpack": "^1.13.3",
|
||||
"whatwg-fetch": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -4,4 +4,4 @@ module.exports = {
|
||||
require('precss')({ /* ...options */ }),
|
||||
require('autoprefixer')({ /* ...options */ })
|
||||
]
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
const express = require('express');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/embed/stream/preview', (req, res) => {
|
||||
res.render('embed-stream', {basePath: '/client/embed/stream'});
|
||||
});
|
||||
|
||||
router.get('*', (req, res) => {
|
||||
res.render('admin', {basePath: '/client/coral-admin'});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -4,7 +4,7 @@ const Comment = require('../../../models/comment');
|
||||
const router = express.Router();
|
||||
|
||||
//==============================================================================
|
||||
// Routes
|
||||
// Get Routes
|
||||
//==============================================================================
|
||||
|
||||
router.get('/', (req, res, next) => {
|
||||
@@ -23,16 +23,54 @@ router.get('/:comment_id', (req, res, next) => {
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/', (req, res, next) => {
|
||||
const {body, author_id, asset_id, parent_id, status, username} = req.body;
|
||||
let comment = new Comment({body, author_id, asset_id, parent_id, status, username});
|
||||
comment.save().then(({id}) => {
|
||||
res.status(200).send({'id': id});
|
||||
//==============================================================================
|
||||
// Moderation Queues Routes
|
||||
//==============================================================================
|
||||
|
||||
router.get('/action/:action_type', (req, res, next) => {
|
||||
Comment.findByActionType(req.params.action_type).then((comments) => {
|
||||
res.status(200).json(comments);
|
||||
}).catch(error => {
|
||||
next(error);
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/status/rejected', (req, res, next) => {
|
||||
Comment.findByStatus('rejected').then((comments) => {
|
||||
res.status(200).json(comments);
|
||||
}).catch(error => {
|
||||
next(error);
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/status/pending', (req, res, next) => {
|
||||
Comment.findByStatus('').then((comments) => {
|
||||
res.status(200).json(comments);
|
||||
}).catch(error => {
|
||||
next(error);
|
||||
});
|
||||
});
|
||||
|
||||
//==============================================================================
|
||||
// Post Routes
|
||||
//==============================================================================
|
||||
|
||||
router.post('/', (req, res, next) => {
|
||||
const {body, author_id, asset_id, parent_id, status, username} = req.body;
|
||||
Comment.new(body, author_id, asset_id, parent_id, status, username).then((comment) => {
|
||||
res.status(200).send({'id': comment.id});
|
||||
}).catch(error => {
|
||||
next(error);
|
||||
});
|
||||
|
||||
// let comment = new Comment({body, author_id, asset_id, parent_id, status, username});
|
||||
// comment.save().then(({id}) => {
|
||||
// res.status(200).send({'id': id});
|
||||
// }).catch(error => {
|
||||
// next(error);
|
||||
// });
|
||||
});
|
||||
|
||||
router.post('/:comment_id', (req, res, next) => {
|
||||
Comment.findById(req.params.comment_id).then((comment) => {
|
||||
comment.body = req.body.body;
|
||||
@@ -48,14 +86,6 @@ router.post('/:comment_id', (req, res, next) => {
|
||||
});
|
||||
});
|
||||
|
||||
router.delete('/:comment_id', (req, res, next) => {
|
||||
Comment.remove(req.params.comment_id).then(() => {
|
||||
res.status(201).send('OK. Deleted');
|
||||
}).catch(error => {
|
||||
next(error);
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/:comment_id/status', (req, res, next) => {
|
||||
Comment.changeStatus(req.params.comment_id, req.body.status).then((comment) => {
|
||||
res.status(200).send(comment);
|
||||
@@ -72,4 +102,16 @@ router.post('/:comment_id/actions', (req, res, next) => {
|
||||
});
|
||||
});
|
||||
|
||||
//==============================================================================
|
||||
// Delete Routes
|
||||
//==============================================================================
|
||||
|
||||
router.delete('/:comment_id', (req, res, next) => {
|
||||
Comment.removeById(req.params.comment_id).then(() => {
|
||||
res.status(201).send('OK. Removed');
|
||||
}).catch(error => {
|
||||
next(error);
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -66,6 +66,86 @@ describe('Get /comments', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Get moderation queues rejected, pending, flags', () => {
|
||||
const comments = [{
|
||||
id: 'abc',
|
||||
body: 'comment 10',
|
||||
asset_id: 'asset',
|
||||
author_id: '123',
|
||||
status: 'rejected'
|
||||
}, {
|
||||
id: 'def',
|
||||
body: 'comment 20',
|
||||
asset_id: 'asset',
|
||||
author_id: '456'
|
||||
}, {
|
||||
id: 'hij',
|
||||
body: 'comment 30',
|
||||
asset_id: '456',
|
||||
status: 'accepted'
|
||||
}];
|
||||
|
||||
const users = [{
|
||||
id: '123',
|
||||
display_name: 'Ana',
|
||||
}, {
|
||||
id: '456',
|
||||
display_name: 'Maria',
|
||||
}];
|
||||
|
||||
const actions = [{
|
||||
action_type: 'flag',
|
||||
item_id: 'abc',
|
||||
item_type: 'comment'
|
||||
}, {
|
||||
action_type: 'like',
|
||||
item_id: 'hij',
|
||||
item_type: 'comment'
|
||||
}];
|
||||
|
||||
beforeEach(() => {
|
||||
return Comment.create(comments).then(() => {
|
||||
return User.create(users);
|
||||
}).then(() => {
|
||||
return Action.create(actions);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return all the rejected comments', function(done){
|
||||
chai.request(app)
|
||||
.get('/api/v1/comments/status/rejected')
|
||||
.end(function(err, res){
|
||||
expect(err).to.be.null;
|
||||
expect(res).to.have.status(200);
|
||||
expect(res.body[0]).to.have.property('id', 'abc');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return all the pending comments', function(done){
|
||||
chai.request(app)
|
||||
.get('/api/v1/comments/status/pending')
|
||||
.end(function(err, res){
|
||||
expect(err).to.be.null;
|
||||
expect(res).to.have.status(200);
|
||||
expect(res.body[0]).to.have.property('id', 'def');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return all the flagged comments', function(done){
|
||||
chai.request(app)
|
||||
.get('/api/v1/comments/action/flag')
|
||||
.end(function(err, res){
|
||||
expect(res).to.have.status(200);
|
||||
expect(err).to.be.null;
|
||||
expect(res.body.length).to.equal(1);
|
||||
expect(res.body[0]).to.have.property('id', 'abc');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Post /comments', () => {
|
||||
const users = [{
|
||||
id: '123',
|
||||
@@ -128,10 +208,12 @@ describe('Get /:comment_id', () => {
|
||||
|
||||
const actions = [{
|
||||
action_type: 'flag',
|
||||
item_id: 'abc'
|
||||
item_id: 'abc',
|
||||
item_type: 'comment'
|
||||
}, {
|
||||
action_type: 'like',
|
||||
item_id: 'hij'
|
||||
item_id: 'hij',
|
||||
item_type: 'comment'
|
||||
}];
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -144,14 +226,12 @@ describe('Get /:comment_id', () => {
|
||||
|
||||
it('should return the right comment for the comment_id', function(done){
|
||||
chai.request(app)
|
||||
.get('/api/v1/comments')
|
||||
.query({'comment_id': 'abc'})
|
||||
.get('/api/v1/comments/abc')
|
||||
.end(function(err, res){
|
||||
const sorted = res.body.sort((a, b) => a.body - b.body);
|
||||
expect(err).to.be.null;
|
||||
expect(res).to.have.status(200);
|
||||
expect(sorted[0]).to.have.property('body')
|
||||
.and.to.equal('comment 10');
|
||||
expect(res).to.have.property('body');
|
||||
expect(res.body).to.have.property('body', 'comment 10');
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -206,14 +286,13 @@ describe('Put /:comment_id', () => {
|
||||
.end(function(err, res){
|
||||
expect(err).to.be.null;
|
||||
expect(res).to.have.status(200);
|
||||
expect(res.body).to.have.property('body');
|
||||
expect(res.body.body).to.equal('Something body.');
|
||||
expect(res.body).to.have.property('body', 'Something body.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Delete /:comment_id', () => {
|
||||
describe('Remove /:comment_id', () => {
|
||||
|
||||
const comments = [{
|
||||
id: 'abc',
|
||||
@@ -259,9 +338,10 @@ describe('Delete /:comment_id', () => {
|
||||
chai.request(app)
|
||||
.delete('/api/v1/comments/abc')
|
||||
.end(function(err, res){
|
||||
expect(err).to.be.null;
|
||||
expect(res).to.have.status(201);
|
||||
Comment.findById({'id': 'abc'}).then((comment) => {
|
||||
expect(comment).to.be.null;
|
||||
Comment.findById('abc').then((comment) => {
|
||||
expect(comment).to.be.empty;
|
||||
});
|
||||
done();
|
||||
});
|
||||
@@ -321,8 +401,7 @@ describe('Post /:comment_id/status', () => {
|
||||
expect(err).to.be.null;
|
||||
expect(res).to.have.status(200);
|
||||
expect(res).to.have.body;
|
||||
expect(res.body).to.have.property('status');
|
||||
expect(res.body.status).to.equal('accepted');
|
||||
expect(res.body).to.have.property('status', 'accepted');
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -381,14 +460,10 @@ describe('Post /:comment_id/actions', () => {
|
||||
expect(err).to.be.null;
|
||||
expect(res).to.have.status(200);
|
||||
expect(res).to.have.body;
|
||||
expect(res.body).to.have.property('item_type');
|
||||
expect(res.body.item_type).to.equal('comment');
|
||||
expect(res.body).to.have.property('action_type');
|
||||
expect(res.body.action_type).to.equal('flag');
|
||||
expect(res.body).to.have.property('item_id');
|
||||
expect(res.body.item_id).to.equal('abc');
|
||||
expect(res.body).to.have.property('user_id');
|
||||
expect(res.body.user_id).to.equal('456');
|
||||
expect(res.body).to.have.property('item_type', 'comment');
|
||||
expect(res.body).to.have.property('action_type', 'flag');
|
||||
expect(res.body).to.have.property('item_id', 'abc');
|
||||
expect(res.body).to.have.property('user_id', '456');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,8 +24,7 @@ describe('GET /settings', () => {
|
||||
expect(err).to.be.null;
|
||||
expect(res).to.have.status(200);
|
||||
expect(res).to.be.json;
|
||||
expect(res.body).to.have.property('moderation');
|
||||
expect(res.body.moderation).to.equal('pre');
|
||||
expect(res.body).to.have.property('moderation', 'pre');
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
@@ -33,30 +32,24 @@ describe('GET /settings', () => {
|
||||
|
||||
// update the settings.
|
||||
describe('update settings', () => {
|
||||
it('should respond ok to a PUT', () => {
|
||||
return Setting.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true})
|
||||
.then(() => {
|
||||
return chai.request(app)
|
||||
.put('/api/v1/settings')
|
||||
.send({moderation: 'post'})
|
||||
.then(res => {
|
||||
expect(res).to.have.status(204);
|
||||
|
||||
before(() => {
|
||||
return Setting.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true});
|
||||
});
|
||||
return Setting.getSettings();
|
||||
|
||||
it('should respond to a PUT with new settings', () => {
|
||||
chai.request(app)
|
||||
.put('/api/v1/settings')
|
||||
.send({moderation: 'post'}, (err, res) => {
|
||||
expect(err).to.be.null;
|
||||
expect(res).to.have.status(204);
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have updates settings', () => {
|
||||
chai.request(app)
|
||||
.get('/api/v1/settings')
|
||||
.end((err, res) => {
|
||||
expect(err).to.be.null;
|
||||
expect(res).to.have.status(200);
|
||||
expect(res).to.be.json;
|
||||
expect(res.body).to.have.property('moderation');
|
||||
expect(res.body.moderation).to.equal('post');
|
||||
}).then(settings => {
|
||||
// confirm updated settings in db
|
||||
expect(settings).to.have.property('moderation');
|
||||
expect(settings.moderation).to.equal('post');
|
||||
}).catch(err => {
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -60,7 +60,6 @@ describe('api/stream: routes', () => {
|
||||
.end(function(err, res){
|
||||
expect(err).to.be.null;
|
||||
expect(res).to.have.status(200);
|
||||
if (err) {return done(err);}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
body, #root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
background: #fff;
|
||||
margin: 0;
|
||||
background: #fff;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@@ -10,13 +10,13 @@
|
||||
body, #root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
background: #fff;
|
||||
margin: 0;
|
||||
background: #fff;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="client/coral-admin/bundle.js" charset="utf-8"></script>
|
||||
<script src="<%= basePath %>/bundle.js" charset="utf-8"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -4,9 +4,9 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="initial-scale=1, maximum-scale=1">
|
||||
<title>Talk - Coral Admin</title>
|
||||
<base href="/client/coral-admin/" />
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||
<link rel="stylesheet" href="https://code.getmdl.io/1.2.1/material.indigo-pink.min.css">
|
||||
<link rel="stylesheet" href="<%= basePath %>/default.css">
|
||||
<style media="screen">
|
||||
body, #root {
|
||||
width: 100%;
|
||||
@@ -17,7 +17,7 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="bundle.js" charset="utf-8"></script>
|
||||
<div id="coralStream"></div>
|
||||
<script src="<%= basePath %>/bundle.js" charset="utf-8"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>Error</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<pre><%= message %></pre>
|
||||
|
||||
<pre><%= error.stack %></pre>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,95 @@
|
||||
const path = require('path');
|
||||
const autoprefixer = require('autoprefixer');
|
||||
const precss = require('precss');
|
||||
const Copy = require('copy-webpack-plugin');
|
||||
const webpack = require('webpack');
|
||||
|
||||
// Edit the build targets and embeds below.
|
||||
|
||||
const buildTargets = [
|
||||
'coral-admin'
|
||||
];
|
||||
|
||||
const buildEmbeds = [
|
||||
'stream'
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
devtool: 'inline-source-map',
|
||||
entry: buildTargets.reduce((entry, target) => {
|
||||
|
||||
// Add the entry for the bundle.
|
||||
entry[`${target}/bundle`] = [
|
||||
'babel-polyfill',
|
||||
path.join(__dirname, 'client/', target, '/src/index')
|
||||
];
|
||||
|
||||
return entry;
|
||||
}, buildEmbeds.reduce((entry, embed) => {
|
||||
|
||||
// Add the entry for the bundle.
|
||||
entry[`embed/${embed}/bundle`] = [
|
||||
'babel-polyfill',
|
||||
path.join(__dirname, 'client/', `coral-embed-${embed}`, '/src/index')
|
||||
];
|
||||
|
||||
return entry;
|
||||
}, {})),
|
||||
output: {
|
||||
path: path.join(__dirname, 'dist'),
|
||||
filename: '[name].js'
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
loader: 'babel',
|
||||
exclude: /node_modules/,
|
||||
test: /\.js$/
|
||||
},
|
||||
{
|
||||
loader: 'json',
|
||||
test: /\.json$/,
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
loaders: [
|
||||
'style-loader',
|
||||
'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',
|
||||
'postcss-loader'
|
||||
],
|
||||
test: /.css$/,
|
||||
},
|
||||
{
|
||||
loader: 'url-loader?limit=100000',
|
||||
test: /\.png$/
|
||||
},
|
||||
{
|
||||
loader: 'file-loader',
|
||||
test: /\.(jpg|png|gif|svg)$/
|
||||
},
|
||||
{
|
||||
loader: 'url?limit=100000',
|
||||
test: /\.woff$/
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new Copy(buildEmbeds.map(embed => ({
|
||||
from: path.join(__dirname, 'client', `coral-embed-${embed}`, 'style'),
|
||||
to: path.join(__dirname, 'dist', 'embed', embed)
|
||||
}))),
|
||||
autoprefixer,
|
||||
precss,
|
||||
new webpack.ProvidePlugin({
|
||||
'fetch': 'imports?this=>global!exports?global.fetch!whatwg-fetch'
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
root: [
|
||||
path.join(__dirname, 'client'),
|
||||
...buildTargets.map(target => path.join(__dirname, 'client', target, 'src')),
|
||||
...buildEmbeds.map(embed => path.join(__dirname, 'client', `coral-embed-${embed}`, 'src'))
|
||||
]
|
||||
},
|
||||
postcss: require('./postcss.config.js')
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
const webpack = require('webpack');
|
||||
const devConfig = require('./webpack.config.dev');
|
||||
|
||||
// Disable source maps.
|
||||
devConfig.devtool = null;
|
||||
|
||||
devConfig.plugins = devConfig.plugins.concat([
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
'NODE_ENV': `"${'production'}"`,
|
||||
'VERSION': `"${require('./package.json')}"`
|
||||
}
|
||||
}),
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
warnings: false
|
||||
}
|
||||
}),
|
||||
new webpack.optimize.OccurrenceOrderPlugin(),
|
||||
new webpack.optimize.DedupePlugin()
|
||||
]);
|
||||
|
||||
module.exports = devConfig;
|
||||
Reference in New Issue
Block a user