...
 
Commits (179)
**/dist
**/node_modules
**/coverage
{
"parser": "babel-eslint",
"extends": [
"standard",
"plugin:react/recommended"
],
"plugins": [
"react"
],
"parserOptions": {
"ecmaVersion": 7,
"ecmaFeatures": {
"jsx": true
}
},
"env": {
"es6": true,
"browser": true
}
},
"extends": ["pubsweet"],
"rules": {
"import/no-dynamic-require": 0,
"import/no-extraneous-dependencies": 0,
"sort-keys": 0
},
"overrides": [
{
"files": ["test/**/*.test.js"],
"globals": {
"fixture": true,
"text": true
}
}
]
}
......@@ -80,13 +80,16 @@ jspm_packages
# pubsweet-specific
myapp
testapp
api
api/db/*
!api/db/.gitkeep
.vscode
.wallaby.js
logs
.env.*
_build
client/dist
server/dist
coverage/
yarn-error.log
package-lock.json
config/local*.*
_build
image: pubsweet/pubsweet-test-base
variables:
IMAGE_ORG: pubsweet
IMAGE_NAME: starter
before_script:
- yarn
stages:
- build
- test
test:
build:
image: docker:19.03.1
services:
- docker:19.03.1-dind
stage: build
script:
- xvfb-run --server-args "-screen 0 1024x768x24" npm test
coverage: '/^All files\s+\|\s+(\d+.\d+)\s\|.*$/'
- docker version
- docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD
- echo "Ignore warning! Cannot perform an interactive login from a non TTY device"
- docker build -t $IMAGE_ORG/$IMAGE_NAME:$CI_COMMIT_SHA .
- docker push $IMAGE_ORG/$IMAGE_NAME:$CI_COMMIT_SHA
test:chrome:
image: $IMAGE_ORG/$IMAGE_NAME:$CI_COMMIT_SHA
stage: test
variables:
DEBUG: nightmare:*
GIT_STRATEGY: none
# setup data for postgres image
POSTGRES_USER: test
POSTGRES_PASSWORD: pw
# connection details for tests
PGUSER: test
PGPASSWORD: pw
NODE_ENV: test
services:
- postgres
script:
- cd ${HOME}
# specify host here else it confuses the linked postgres image
- PGHOST=postgres npx testcafe 'chrome:headless --no-sandbox' test/**/*.test.js
test:firefox:
image: $IMAGE_ORG/$IMAGE_NAME:$CI_COMMIT_SHA
stage: test
variables:
GIT_STRATEGY: none
# setup data for postgres image
POSTGRES_USER: test
POSTGRES_PASSWORD: pw
# connection details for tests
PGUSER: test
PGPASSWORD: pw
NODE_ENV: test
services:
- postgres
script:
- cd ${HOME}
# specify host here else it confuses the linked postgres image
- PGHOST=postgres npx testcafe firefox:headless test/**/*.test.js
lint:
image: $IMAGE_ORG/$IMAGE_NAME:$CI_COMMIT_SHA
stage: test
variables:
GIT_STRATEGY: none
script:
- cd ${HOME}
- npm run lint
{
"*.{js,jsx}": ["prettier --write", "eslint --fix", "git add"],
"*.{json,md,css,scss}": ["prettier --write", "git add"]
}
{
"semi": false,
"singleQuote": true,
"trailingComma": "all"
}
FROM node:12
ENV HOME "/home/pubsweet"
RUN mkdir -p ${HOME}
WORKDIR ${HOME}
# Install chrome and firefox
RUN curl -sL http://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
RUN echo 'deb http://dl.google.com/linux/chrome/deb/ stable main' >> /etc/apt/sources.list.d/google.list
RUN apt-get update && apt-get install -y google-chrome-stable
# Apparently no debian package for firefox 64
RUN apt-get install -y libdbus-glib-1-2
RUN cd /opt && wget https://ftp.mozilla.org/pub/firefox/releases/64.0.2/linux-x86_64/en-GB/firefox-64.0.2.tar.bz2 && \
tar xjf firefox-*.tar.bz2 && \
ln -s /opt/firefox/firefox /usr/local/bin/
COPY package.json yarn.lock ./
RUN [ "yarn", "install", "--frozen-lockfile" ]
# Remove cache and offline mirror
RUN [ "yarn", "cache", "clean"]
RUN [ "rm", "-rf", "/npm-packages-offline-cache"]
COPY .eslintignore .eslintrc .prettierrc ./
COPY public public
COPY app app
COPY scripts scripts
COPY config config
COPY server server
COPY test test
COPY webpack webpack
ENV NODE_ENV "production"
RUN [ "npx", "pubsweet", "build"]
EXPOSE 3000
CMD []
......@@ -5,14 +5,16 @@ Welcome to the skeleton application!
## Quickstart
```bash
cd my-app-name
pubsweet setupdb (or npm run setupdb)
pubsweet start (or npm start)
pubsweet start # or npm start
```
This will use Docker to get you up and running quickly. See `pubsweet` docs for
troubleshooting and other ways to start the app.
## Customising the skeleton application
- Change `name` in `package.json` to your desired name
- Modify app config in the `/config` directory and webpack config in `/webpack`.
- Run `pubsweet add <components>` or `pubsweet remove <components>` to add or remove components. Mount these components on routes in `app/routes.jsx`
- Rewrite this README!
* Change `name` in `package.json` to your desired name
* Modify app config in the `/config` directory and webpack config in `/webpack`.
* Run `pubsweet add <components>` or `pubsweet remove <components>` to add or
remove components.
* Rewrite this README!
{
"presets": [
"env",
"react",
"stage-2"
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-syntax-import-meta",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-json-strings",
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
"@babel/plugin-proposal-function-sent",
"@babel/plugin-proposal-export-namespace-from",
"@babel/plugin-proposal-numeric-separator",
"@babel/plugin-proposal-throw-expressions"
]
}
// Use this file as a starting point for your project's .eslintrc.
// Copy this file, and add rule overrides as needed.
{
"extends": ["standard", "standard-react"]
}
......@@ -2,34 +2,22 @@ import 'regenerator-runtime/runtime'
import React from 'react'
import ReactDOM from 'react-dom'
import { hot } from 'react-hot-loader'
import { configureStore, Root } from 'pubsweet-client'
import { Root } from 'pubsweet-client'
import { AppContainer } from 'react-hot-loader'
import createHistory from 'history/createBrowserHistory'
import theme from '@pubsweet/coko-theme'
import { createBrowserHistory } from 'history'
import routes from './routes'
const history = createHistory()
const store = configureStore(history, {})
const history = createBrowserHistory()
const rootEl = document.getElementById('root')
ReactDOM.render(
<AppContainer>
<Root store={store} history={history} routes={routes} />
</AppContainer>,
rootEl
<Root history={history} routes={routes} theme={theme} />,
rootEl,
)
if (module.hot) {
module.hot.accept('pubsweet-client/src/components/Root', () => {
const NextRoot = require('pubsweet-client/src/components/Root').default
ReactDOM.render(
<AppContainer>
<NextRoot store={store} history={history} routes={routes} />
</AppContainer>,
rootEl
)
})
}
export default hot(module)(Root)
import React from 'react'
import PropTypes from 'prop-types'
const App = ({ children, ...props }) => <div>{children}</div>
App.propTypes = {
children: PropTypes.node.isRequired,
}
export default App
import React from 'react'
import PropTypes from 'prop-types'
import AuthenticatedComponent from 'pubsweet-client/src/components/AuthenticatedComponent'
import Manage from 'pubsweet-component-manage/Manage'
import Navigation from '../Navigation/Navigation'
const AuthenticatedManage = ({children, ...props}) => (
<Manage nav={<Navigation />}>
<AuthenticatedComponent
operation='GET'
selector={(state) => state.collections[0]}
unauthorized={<p>You are not authorized to view this page.</p>}
{...props}
>
{children}
</AuthenticatedComponent>
</Manage>
)
AuthenticatedManage.propTypes = {
children: PropTypes.node
}
export default AuthenticatedManage
import React from 'react'
import { Route, Switch, Redirect } from 'react-router-dom'
import AuthenticatedComponent from 'pubsweet-client/src/components/AuthenticatedComponent'
import ConnectedNavigation from './Navigation/ConnectedNavigation'
import HelloWorld from './HelloWorld'
import KitchenSink from './KitchenSink'
const Dashboard = () => (
<>
{/* Everything in the dashboard is for authenticated users only */}
<AuthenticatedComponent>
<ConnectedNavigation />
<Switch>
<Redirect exact path="/dashboard" to="/dashboard/hello-world" />
<Route component={HelloWorld} path="/dashboard/hello-world" />
<Route component={KitchenSink} path="/dashboard/kitchen-sink" />
</Switch>
</AuthenticatedComponent>
</>
)
export default Dashboard
import React from 'react'
const HelloWorld = () => <div>Hello World!</div>
export default HelloWorld
import React, { useState } from 'react'
import {
H1,
Link,
Icon,
StateItem,
TextArea,
TextField,
Steps,
CenteredColumn,
Section,
} from '@pubsweet/ui'
import { NoteEditor } from 'xpub-edit'
const HelloWorld = () => {
const stateItemValues = ['To Clean', 'Cleaning', 'Cleaned']
const [stateItemIndex, setIndex] = useState(0)
const [textAreaValue, setTextAreaValue] = useState(
'A text area you can write in...',
)
const [textFieldValue, setTextFieldValue] = useState(
'And a text field to write in too!',
)
const [currentStep, setCurrentStep] = useState(0)
return (
<CenteredColumn>
<H1>A little kitchen sink garden</H1>
<Section>
This is a small number of components available in PubSweet! For more go
to <Link to="http://pubsweet.coko.foundation">our docs.</Link>
</Section>
<Section>
<StateItem
index={stateItemIndex}
update={(_, nextIndex) => setIndex(nextIndex)}
values={stateItemValues}
/>
</Section>
<Section>
<TextArea
label="Foo"
onChange={event => setTextAreaValue(event.target.value)}
placeholder="so you can write some in here"
value={textAreaValue}
/>
</Section>
<Section>
<TextField
label="Foo"
onChange={event => setTextFieldValue(event.target.value)}
placeholder="so you can write some in here"
value={textFieldValue}
/>
</Section>
<Section>
<p>There are things like a step/wizard component:</p>
<Steps currentStep={currentStep} margin="40px 50px">
<Steps.Step title="First step" />
<Steps.Step title="Second step" />
<Steps.Step title="Third step" />
</Steps>
<button
onClick={() => {
if (currentStep > 0) {
setCurrentStep(currentStep - 1)
}
}}
>
Prev
</button>
<button
onClick={() => {
if (currentStep < 3) {
setCurrentStep(currentStep + 1)
}
}}
>
Next
</button>
</Section>
<Section>
<p>Or icons... </p>
<Icon size={6}>check_circle</Icon>
</Section>
<Section>
<p>Or more complete editors, like this one</p>
<NoteEditor
onBlur={value => value}
onChange={value => value}
placeholder="Enter a message…"
title="Note"
value="I'm a more complete editor!"
/>
</Section>
</CenteredColumn>
)
}
export default HelloWorld
import React from 'react'
import { Action, ActionGroup } from '@pubsweet/ui'
const LandingPage = () => (
<>
<div>Hello World! Your PubSweet application is running just fine!</div>
<ActionGroup>
<Action to="/login">Login</Action>
<Action to="/signup">Signup</Action>
<Action to="/dashboard">Dashboard</Action>
</ActionGroup>
</>
)
export default LandingPage
import React from 'react'
import { Query, ApolloConsumer } from '@apollo/react-components'
import gql from 'graphql-tag'
import Navigation from './Navigation'
const CURRENT_USER = gql`
query CurrentUser {
currentUser {
id
username
admin
}
}
`
const ConnectedNavigation = props => (
<Query query={CURRENT_USER}>
{({ loading, error, data }) => {
if (loading) return 'Loading...'
return (
<ApolloConsumer>
{client => (
<Navigation
client={client}
currentUser={data.currentUser}
loading={loading}
{...props}
/>
)}
</ApolloConsumer>
)
}}
</Query>
)
export default ConnectedNavigation
import React from 'react'
import PropTypes from 'prop-types'
import { LinkContainer } from 'react-router-bootstrap'
import { Navbar, Nav, NavItem, NavbarBrand } from 'react-bootstrap'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { withRouter } from 'react-router-dom'
import { compose, withProps } from 'recompose'
import { AppBar, Action } from '@pubsweet/ui'
import logo from './pubsweet.jpg'
import Authorize from 'pubsweet-client/src/helpers/Authorize'
import NavbarUser from 'pubsweet-component-navigation/NavbarUser'
import actions from 'pubsweet-client/src/actions'
const Navigation = ({ logoutUser, currentUser, client }) => {
const links = [
<Action to="/dashboard/hello-world">Hello World</Action>,
<Action to="/dashboard/kitchen-sink">Kitchen Sink</Action>,
]
export class Navigation extends React.Component {
render () {
const { actions, currentUser } = this.props
let logoutButtonIfAuthenticated
if (currentUser.isAuthenticated) {
logoutButtonIfAuthenticated = <NavbarUser
user={currentUser.user}
onLogoutClick={actions.logoutUser}
return (
<div>
<AppBar
brand={<img alt="pubsweet" src={logo} />}
navLinkComponents={links}
onLogoutClick={() => logoutUser(client)}
user={currentUser}
/>
}
return (
<Navbar fluid>
<Navbar.Header>
<NavbarBrand>
<a href='#'><img src='/assets/pubsweet.jpg' alt='pubsweet' /></a>
</NavbarBrand>
</Navbar.Header>
<Nav>
<LinkContainer to='/manage/posts'>
<NavItem>Posts</NavItem>
</LinkContainer>
<Authorize operation='GET' object={{path: '/users'}}>
<LinkContainer to='/manage/users'>
<NavItem>Users</NavItem>
</LinkContainer>
</Authorize>
<Authorize operation='GET' object={{path: '/teams'}}>
<LinkContainer to='/manage/teams'>
<NavItem>Teams</NavItem>
</LinkContainer>
</Authorize>
</Nav>
{ logoutButtonIfAuthenticated }
</Navbar>
)
}
</div>
)
}
Navigation.propTypes = {
actions: PropTypes.object.isRequired,
currentUser: PropTypes.object
client: PropTypes.any, // eslint-disable-line
currentUser: PropTypes.any, // eslint-disable-line
history: PropTypes.any.isRequired, // eslint-disable-line
logoutUser: PropTypes.func.isRequired,
}
function mapState (state) {
return {
currentUser: state.currentUser
}
}
function mapDispatch (dispatch) {
return {
actions: bindActionCreators(actions, dispatch)
}
}
export default connect(mapState, mapDispatch)(Navigation)
export default compose(
withRouter,
withProps(props => ({
logoutUser: client => {
client.cache.reset()
localStorage.removeItem('token')
props.history.push('/login')
},
})),
)(Navigation)
import React from 'react'
import PropTypes from 'prop-types'
import { Query } from '@apollo/react-components'
import gql from 'graphql-tag'
const CURRENT_USER = gql`
query CurrentUser {
currentUser {
id
username
admin
}
}
`
const CurrentUserQuery = props => {
const { render } = props
return (
<Query fetchPolicy="network-only" query={CURRENT_USER}>
{render}
</Query>
)
}
CurrentUserQuery.propTypes = {
render: PropTypes.any, // eslint-disable-line
}
export default CurrentUserQuery
......@@ -5,6 +5,6 @@
</head>
<body>
<div id="root"></div>
<script type="text/javascript" charset="utf-8" src="/assets/app.js"></script>
<script type="text/javascript" charset="utf-8" src="/assets/js/bundle.js"></script>
</body>
</html>
import React from 'react'
import { Route, Switch } from 'react-router-dom'
// Manage
import AuthenticatedManage from './components/AuthenticatedManage/AuthenticatedManage'
import PostsManager from 'pubsweet-component-posts-manager/PostsManagerContainer'
import UsersManager from 'pubsweet-component-users-manager/UsersManagerContainer'
import TeamsManager from 'pubsweet-component-teams-manager/TeamsManagerContainer'
// Editor
import MediumDraft from 'pubsweet-component-medium-draft/MediumDraftContainer'
// Authentication
import Login from 'pubsweet-component-login'
import Signup from 'pubsweet-component-signup'
import PasswordReset from '@pubsweet/component-password-reset-client'
// Public
import Blog from 'pubsweet-component-blog/BlogContainer'
import HTML from 'pubsweet-component-html/HTMLContainer'
import App from './components/App'
// Authentication
import Login from 'pubsweet-component-login/LoginContainer'
import Signup from 'pubsweet-component-signup/SignupContainer'
// This is your presentation side of things
import LandingPage from './components/LandingPage'
class Managed extends React.Component {
render () {
return <AuthenticatedManage>
<Switch>
<Route path='/manage/posts' component={PostsManager} />
<Route path='/manage/users' component={UsersManager} />
<Route path='/manage/teams' component={TeamsManager} />
<Route path='/manage/sciencewriter/:id' component={MediumDraft} />
</Switch>
</AuthenticatedManage>
}
}
// And this is where your admin stuff goes
import Dashboard from './components/Dashboard'
export default (
<Switch>
<Route exact path='/' component={Blog} />
<Route path='/manage' component={Managed} />
<Route path='/login' component={Login} />
<Route path='/signup' component={Signup} />
<Route path='/:id' component={HTML} />
</Switch>
<App>
<Switch>
<Route component={LandingPage} exact path="/" />
<Route component={Dashboard} path="/dashboard" />
<Route component={Login} path="/login" />
<Route component={Signup} path="/signup" />
<Route component={PasswordReset} path="/password-reset" />
</Switch>
</App>
)
module.exports = {
before: async (userId, operation, object, context) => true,
// const user = userId && (await context.models.User.find(userId))
// return user && user.admin
}
const blogmode = require('authsome/src/modes/blog')
const Joi = require('joi')
module.exports = {
'pubsweet-client': {
API_ENDPOINT: '/api',
theme: 'PepperTheme',
'login-redirect': '/manage/posts',
'redux-log': true
},
authsome: {
mode: blogmode,
teams: {
teamContributors: {
name: 'Contributors',
permissions: 'POST'
},
teamCoauthors: {
name: 'Coauthors',
permissions: 'PATCH'
}
}
},
pubsweet: {
components: require('./components.json')
},
validations: {
fragment: {
kind: Joi.string(),
published: Joi.bool(),
published_at: Joi.string(),
source: Joi.any(),
presentation: Joi.string()
}
}
}
const config = require('./client-default.js')
module.exports = config
const config = require('./client-default.js')
module.exports = config
const config = require('./client-default.js')
module.exports = config
[
"pubsweet-component-blog",
"pubsweet-component-login",
"pubsweet-component-manage",
"pubsweet-component-pepper-theme",
"pubsweet-component-posts-manager",
"pubsweet-component-signup"
"@pubsweet/model-user",
"@pubsweet/model-team",
"@pubsweet/model-blog",
"@pubsweet/model-blogpost",
"@pubsweet/component-password-reset-server",
"@pubsweet/job-xsweet"
]
module.exports = {
'pubsweet-server': {
dbPath: 'PUBSWEET_DB',
sse: 'PUBSWEET_SSE'
}
sse: 'PUBSWEET_SSE',
port: 'PORT',
},
watchTests: 'WATCH_TESTS',
}
const path = require('path')
const blogmode = require('authsome/src/modes/blog')
const Joi = require('joi')
const logger = require('winston')
const components = require('./components.json')
module.exports = {
'pubsweet-server': {
dbPath: path.join(__dirname, '..', 'api', 'db', 'development'),
db: {
database: 'test',
},
logger,
API_ENDPOINT: '/api'
port: 3000,
uploads: 'uploads',
morganLogFormat: 'combined',
},
'pubsweet-client': {
API_ENDPOINT: '/api',
theme: 'PepperTheme',
'login-redirect': '/dashboard',
},
authsome: {
mode: blogmode,
// this should be either an npm package or an absolute path, not a relative path
mode: path.resolve(__dirname, './authsome-mode.js'),
teams: {
teamContributors: {
name: 'Contributors',
permissions: 'POST'
seniorUser: {
name: 'Senior User',
},
simpleUser: {
name: 'Simple User',
},
teamCoauthors: {
name: 'Coauthors',
permissions: 'PATCH'
}
}
},
},
pubsweet: {
components: require('./components.json')
components,
},
'password-reset': {
url: 'http://localhost:3000/password-reset',
sender: 'noreply@pubsweet.org',
},
mailer: {
path: path.join(__dirname, 'mailer'),
from: 'nobody@example.com',
},
validations: {
fragment: {
kind: Joi.string(),
published: Joi.bool(),
published_at: Joi.string(),
source: Joi.any(),
presentation: Joi.string()
}
}
publicKeys: ['pubsweet-client', 'authsome', 'pubsweet', 'validations'],
}
module.exports = {
'pubsweet-server': {
db: {
database: 'starter',
},
baseUrl: `http://localhost:4000`,
morganLogFormat:
':method :url :status :graphql[operation] :res[content-length] :response-time ms',
},
dbManager: {
username: 'admin',
password: 'password',
email: 'admin@example.com',
admin: true,
},
}
{
"pubsweet-server": {
"secret": "hello"
}
}
{
"pubsweet-server": {
"secret": "hello"
}
}
module.exports = {
transport: {
sendmail: true,
},
}
module.exports = {
transport: {
sendmail: false,
port: 1025,
tls: { rejectUnauthorized: false },
auth: {
user: 'user',
pass: 'pass',
},
},
}
const path = require('path')
module.exports = {
'pubsweet-server': {
dbPath: path.join(__dirname, '..', 'api', 'db', 'production')
}
// this must be provided
baseUrl: undefined,
},
}
const logger = require('winston')
const path = require('path')
module.exports = {
'pubsweet-server': {
dbPath: path.join(__dirname, '..', 'api', 'db', 'test'),
secret: 'test'
db: { database: 'test' },
logger,
port: 4000,
baseUrl: 'http://localhost:4000',
secret: 'test',
},
mailer: {
path: path.join(__dirname, 'mailer_test.js'),
},
'password-reset': {
url: 'http://localhost:3000/password-reset',
},
// prevent logging from swaming test output
logger: {
debug: () => false,
info: () => false,
warn: () => false,
error: () => false
}
}
version: '2.1'
services:
web:
image: pubsweet/pubsweet:base
command: sh -c "yarn && yarn seed && yarn server"
ports:
- ${PORT:-3000}:3000
volumes:
- .:/app
working_dir: /app
environment:
PGHOST: db
PGUSER: $USER
db:
image: postgres:10
ports:
- 5432:5432
environment:
POSTGRES_USER: $USER
POSTGRES_DB: starter
volumes:
- ./data/postgres:/var/lib/postgresql/data
- ./scripts/test.sql:/docker-entrypoint-initdb.d/test.sql
FROM ubuntu:17.10
RUN apt-get update && apt-get install -y curl build-essential git
RUN curl -sL https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \
echo "deb https://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list && \
curl -sL https://deb.nodesource.com/setup_8.x | bash -
RUN apt-get install -y nodejs firefox google-chrome-stable && \
mkdir /tests && \
npm i -g yarn
ENTRYPOINT bash
WORKDIR /tests
\ No newline at end of file
......@@ -8,91 +8,100 @@
"static",
"webpack"
],
"main": "app.js",
"dependencies": {
"@pubsweet/logger": "^0.0.1",
"authsome": "0.0.9",
"jest": "^21.1.0",
"pubsweet": "1.0.0-alpha.6",
"pubsweet-client": "1.0.0-beta.6",
"pubsweet-component-blog": "^0.3.1",
"pubsweet-component-html": "^0.2.0",
"pubsweet-component-login": "^0.5.1",
"pubsweet-component-manage": "^0.1.11",
"pubsweet-component-medium-draft": "^0.2.0",
"pubsweet-component-navigation": "^0.2.7",
"pubsweet-component-pepper-theme": "^0.0.3",
"pubsweet-component-posts-manager": "^0.4.2",
"pubsweet-component-signup": "^0.4.0",
"pubsweet-component-teams-manager": "^0.2.1",
"pubsweet-component-users-manager": "^0.2.1",
"pubsweet-server": "1.0.0-alpha.4",
"pubsweet-theme-plugin": "^0.0.1",
"react-router-redux": "next"
"@apollo/react-common": "^3.1.3",
"@apollo/react-hoc": "^3.1.3",
"@pubsweet/coko-theme": "^5.1.21",
"@pubsweet/component-password-reset-client": "^3.1.20",
"@pubsweet/component-password-reset-server": "^2.2.4",
"@pubsweet/db-manager": "3.1.4",
"@pubsweet/default-theme": "4.1.18",
"@pubsweet/job-xsweet": "^2.1.4",
"@pubsweet/logger": "^0.2.39",
"@pubsweet/model-blog": "^0.0.32",
"@pubsweet/model-blogpost": "^0.0.32",
"@pubsweet/model-team": "^3.0.2",
"@pubsweet/model-user": "^5.1.9",
"@pubsweet/models": "0.3.4",
"@pubsweet/ui": "^12.3.0",
"authsome": "^0.1.0",
"mini-css-extract-plugin": "^0.9.0",
"pubsweet": "^5.1.7",
"pubsweet-client": "^10.1.4",
"pubsweet-component-login": "^3.0.21",
"pubsweet-component-signup": "^2.1.15",
"pubsweet-server": "^13.9.1",
"xpub-edit": "^2.6.4"
},
"devDependencies": {
"@babel/core": "^7.8.4",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-proposal-decorators": "^7.8.3",
"@babel/plugin-proposal-export-namespace-from": "^7.8.3",
"@babel/plugin-proposal-function-sent": "^7.8.3",
"@babel/plugin-proposal-json-strings": "^7.8.3",
"@babel/plugin-proposal-numeric-separator": "^7.8.3",
"@babel/plugin-proposal-throw-expressions": "^7.8.3",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-syntax-import-meta": "^7.8.3",
"@babel/preset-env": "^7.8.4",
"@babel/preset-react": "^7.8.3",
"app-module-path": "^2.2.0",
"autobind-decorator": "^1.3.4",
"babel-core": "^6.14.0",
"babel-eslint": "^8.0.0",
"babel-loader": "^7.0.0",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-preset-babili": "0.0.12",
"babel-preset-env": "^1.6.0",
"babel-preset-es2015": "^6.14.0",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.0.0",
"babel-preset-es2015-native-modules": "^6.9.4",
"babel-preset-react": "^6.11.1",
"babel-preset-stage-2": "^6.13.0",
"babili-webpack-plugin": "^0.0.11",
"bootstrap-sass": "^3.3.7",
"compression-webpack-plugin": "^0.3.1",
"copy-webpack-plugin": "^4.0.1",
"css-loader": "^0.28.1",
"eslint": "^4.7.1",
"eslint-config-standard": "^10.2.1",
"eslint-config-standard-react": "^5.0.0",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-node": "^5.2.0",
"eslint-plugin-react": "^7.4.0",
"eslint-plugin-standard": "^3.0.1",
"extract-text-webpack-plugin": "^2.0.0-beta.4",
"file-loader": "^0.11.1",
"html-webpack-plugin": "^2.24.0",
"joi-browser": "^10.0.6",
"babel-preset-minify": "^0.5.0",
"compression-webpack-plugin": "^3.1.0",
"copy-webpack-plugin": "^5.1.1",
"css-loader": "^3.4.2",
"eslint": "^6.8.0",
"eslint-config-pubsweet": "^0.0.6",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-jest": "^23.8.0",
"eslint-plugin-jsx-a11y": "^6.0.2",
"eslint-plugin-node": "^11.0.0",
"eslint-plugin-prettier": "^3.1.2",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-react": "^7.18.3",
"eslint-plugin-standard": "^4.0.1",
"faker": "^4.1.0",
"file-loader": "^5.1.0",
"fs-extra": "^8.1.0",
"html-webpack-plugin": "^3.2.0",
"husky": "^4.2.3",
"joi-browser": "^13.0.1",
"json-loader": "^0.5.4",
"nightmare": "segmentio/nightmare",
"nightmare-wait-for-url": "^0.0.2",
"node-sass": "^4.5.2",
"pouchdb-adapter-memory": "^6.1.1",
"react-hot-loader": "^3.0.0-beta.6",
"regenerator-runtime": "^0.11.0",
"sass-loader": "^6.0.3",
"lint-staged": "^10.0.8",
"node-dev": "^4.0.0",
"node-sass": "^4.13.1",
"prettier": "^1.10.2",
"react-hot-loader": "^4.12.19",
"regenerator-runtime": "^0.13.3",
"script-loader": "^0.7.0",
"standard": "^10.0.1",
"string-replace-loader": "^1.2.0",
"style-loader": "^0.17.0",
"url-loader": "^0.5.8",
"webpack": "^2.4.1",
"webpack-dev-middleware": "^1.10.2",
"webpack-hot-middleware": "^2.18.0"
"smtp-server": "^3.3.0",
"style-loader": "^1.1.3",
"testcafe": "^1.8.2",
"testcafe-react-selectors": "^4.0.0",
"url-loader": "^3.0.0",
"webpack": "^4.41.6",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3",
"webpack-hot-middleware": "^2.25.0"
},
"scripts": {
"lint": "eslint --ext js,jsx app test webpack",
"start": "pubsweet start",
"setupdb": "pubsweet setupdb",
"test": "jest"
},
"jest": {
"testMatch": [
"**/test/*.js"
],
"modulePaths": [
"<rootDir>/node_modules"
],
"testEnvironment": "node",
"unmockedModulePathPatterns": [
"/src/models"
]
"lint": "eslint --ext js,jsx app config test webpack",
"start": "docker-compose up",
"start:services": "docker-compose up db",
"start:client": "pubsweet start:client",
"start:server": "pubsweet start:server",
"seed": "node scripts/seed.js",
"precommit": "lint-staged",
"test": "NODE_ENV=test NODE_PRESERVE_SYMLINKS=1 testcafe chrome 'test/**/*.test.js'"
},
"repository": "https://gitlab.coko.foundation/pubsweet/pubsweet-starter",
"license": "MIT"
"license": "MIT",
"resolutions": {
"bcrypt": "3.0.6"
}
}
#!/usr/bin/env node
const logger = require('@pubsweet/logger')
const { Collection, User } = require('@pubsweet/models')
const { setupDb } = require('@pubsweet/db-manager')
const seed = async () => {
await setupDb({
username: 'admin',
password: 'password',
email: 'admin@example.com',
admin: true,
clobber: true,
})
const user = await new User({
username: 'john',
email: 'john@example.com',
password: 'johnjohn',
}).save()
const collection = new Collection({
title: 'My Blog',
owners: [user.id],
})
await collection.save()
logger.info('Seeding complete. Kill this script with Ctrl-C.')
}
seed()
CREATE DATABASE test;
\ No newline at end of file
The started application's server is entirely composed out of components (see `config/components.json`), so this folder is empty.
You can put your custom server components here (see the docs at https://pubsweet.coko.foundation/ for more information).
{
"env": {
"node": true,
"es6": true,
"browser": true,
"jest": true,
"jasmine": true
}
}
import faker from 'faker'
import config from 'config'
import { startServer, setup } from './helpers/setup'
import mailHelper from './helpers/mail'
import { login, passwordReset, helloWorld } from './pageObjects'
let admin
fixture('Admin user')
.page(passwordReset.url)
.before(startServer)
.beforeEach(async () => {
const result = await setup()
admin = result.userData
})
// eslint-disable-next-line jest/no-test-callback
test('Password reset journey', async t => {
// start mail server
mailHelper.start()
// request password reset email
await t
.typeText(passwordReset.username, admin.username)
.click(passwordReset.submit)
.expect(passwordReset.alert.innerText)
.contains('email has been sent')
// extract reset URL from email content
const mail = await mailHelper.nextEmail()
const matchResult = mail
.replace(/=3D/g, '=')
.replace(/=\r?\n/g, '')
.match(new RegExp(`${config.get('pubsweet-server.baseUrl')}\\S+`))
if (!matchResult) throw new Error('URL not found in email')
// set new password
const newPass = faker.internet.password()
await t
.navigateTo(matchResult[0])
.typeText(passwordReset.password, newPass)
.click(passwordReset.submit)
// login
await login
.doLogin(admin.username, newPass)
.expect(helloWorld.text.innerText)
.eql('Hello World!')
})
module.exports = {
dbconfig: {
username: 'fakeymcfake',
email: 'fakey_mcfake@pseudonymous.com',
password: 'correct battery horse staple',
collection: 'super secret scintillatingly scandalous documents'
},
regularuser: {
username: 'fakeymcnormal',
email: 'fakey_mcfakerer@pseudonymous.com',
password: 'incorrect solar zebra paperclip',
admin: false
},
adminuser: {
username: 'fakeymcadmin',
email: 'fakey_mcfakererer@pseudonymous.com',
password: 'pa55w0rd',
admin: true
}
}
import faker from 'faker'
import { login, signup } from './pageObjects'
import { startServer, setup } from './helpers/setup'
fixture('Guest user')
.before(startServer)
.beforeEach(async () => {
await setup()
})
// eslint-disable-next-line jest/no-test-callback
test('Signup journey', async t => {
const user = {
username: faker.internet.domainWord(),
email: faker.internet.exampleEmail(),
password: faker.internet.password(),
}
// cannot log in
await login
.doLogin(user.username, user.password)
.expect(login.alert.innerText)
.contains('Wrong username or password.')
// signup
await t
.click(login.signUp)
.expect(signup.title.innerText)
.contains('Sign up')
.typeText(signup.username, user.username)
.typeText(signup.email, user.email)
.typeText(signup.password, user.password)
.click(signup.submit)
// can log in
await login
.doLogin(user.username, user.password)
.expect(t.eval(() => window.location.href))
.eql('http://localhost:4000/dashboard/hello-world')
})
const Nightmare = require('nightmare')
require('nightmare-wait-for-url')
Nightmare.action('getattr', function (selector, attr, done) {