Commit 70b1c81f authored by Jure's avatar Jure

Merge branch 'update_deps' into 'master'

Update deps

See merge request !44
parents db83a27d 41bd47e0
Pipeline #9081 passed with stages
in 7 minutes and 33 seconds
......@@ -6,7 +6,7 @@ import { hot } from 'react-hot-loader'
import { configureStore, Root } from 'pubsweet-client'
import theme from '@pubsweet/default-theme'
import theme from '@pubsweet/coko-theme'
import createHistory from 'history/createBrowserHistory'
import routes from './routes'
......
import React from 'react'
import PropTypes from 'prop-types'
import Navigation from './Navigation/Navigation'
const App = ({ children, ...props }) => (
<div>
<Navigation />
{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 Navigation from '../Navigation/Navigation'
const AuthenticatedManage = ({ children, ...props }) => (
<div>
<Navigation />
<AuthenticatedComponent
operation="GET"
selector={state => state.collections[0]}
unauthorized={<p>You are not authorized to view this page.</p>}
{...props}
>
{children}
</AuthenticatedComponent>
</div>
)
AuthenticatedManage.propTypes = {
children: PropTypes.node.isRequired,
}
export default AuthenticatedManage
import React from 'react'
import PropTypes from 'prop-types'
import { NavLink } from 'react-router-dom'
import { connect } from 'react-redux'
import Authorize from 'pubsweet-client/src/helpers/Authorize'
import actions from 'pubsweet-client/src/actions'
import { AppBar } from '@pubsweet/ui'
import { Action, AppBar } from '@pubsweet/ui'
const Navigation = ({ logoutUser, currentUser }) => (
<AppBar
brand={<img alt="pubsweet" src="/assets/pubsweet.jpg" />}
navLinks={
<div>
<NavLink to="/manage/posts">Posts</NavLink>
<Authorize object={{ path: '/users' }} operation="GET">
<NavLink to="/manage/users">Users</NavLink>
</Authorize>
<Authorize object={{ path: '/teams' }} operation="GET">
<NavLink to="/manage/teams">Teams</NavLink>
</Authorize>
</div>
}
navLinkComponents={[
<Action to="/manage/posts">Posts</Action>,
<Authorize object={{ path: '/users' }} operation="GET">
<Action to="/manage/users">Users</Action>
</Authorize>,
]}
onLogoutClick={logoutUser}
user={currentUser}
/>
......
......@@ -3,12 +3,9 @@ import { Route, Switch } from 'react-router-dom'
// Manage
import PostsManager from 'pubsweet-component-posts-manager/PostsManagerContainer'
import UsersManager from 'pubsweet-component-users-manager/UsersManagerContainer'
import UsersManager from 'pubsweet-component-users-manager/src/UsersManagerContainer'
import TeamsManager from 'pubsweet-component-teams-manager/src/TeamsManagerContainer'
// Editor
import MediumDraft from 'pubsweet-component-medium-draft/MediumDraftContainer'
// Public
import Blog from 'pubsweet-component-blog/BlogContainer'
import HTML from 'pubsweet-component-html/HTMLContainer'
......@@ -17,27 +14,33 @@ import HTML from 'pubsweet-component-html/HTMLContainer'
import Login from 'pubsweet-component-login/LoginContainer'
import Signup from 'pubsweet-component-signup/SignupContainer'
import PasswordReset from 'pubsweet-component-password-reset-frontend/PasswordReset'
import AuthenticatedComponent from 'pubsweet-client/src/components/AuthenticatedComponent'
import AuthenticatedManage from './components/AuthenticatedManage/AuthenticatedManage'
import App from './components/App'
const Managed = () => (
<AuthenticatedManage>
<Switch>
<Route component={PostsManager} path="/manage/posts" />
<Route component={UsersManager} path="/manage/users" />
<Route component={TeamsManager} path="/manage/teams" />
<Route component={MediumDraft} path="/manage/sciencewriter/:id" />
</Switch>
</AuthenticatedManage>
// eslint-disable-next-line
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props => (
<AuthenticatedComponent>
<Component {...props} />
</AuthenticatedComponent>
)}
/>
)
export default (
<Switch>
<Route component={Blog} exact path="/" />
<Route component={Managed} path="/manage" />
<Route component={Login} path="/login" />
<Route component={Signup} path="/signup" />
<Route component={PasswordReset} path="/password-reset" />
<Route component={HTML} path="/:id" />
</Switch>
<App>
<Switch>
<PrivateRoute component={UsersManager} path="/manage/users" />
<PrivateRoute component={PostsManager} path="/manage/posts" />
<PrivateRoute component={TeamsManager} path="/manage/teams" />
<Route component={Blog} exact path="/" />
<Route component={Login} path="/login" />
<Route component={Signup} path="/signup" />
<Route component={PasswordReset} path="/password-reset" />
<Route component={HTML} path="/:id" />
</Switch>
</App>
)
const { pickBy } = require('lodash')
class AuthsomeMode {
/**
* Creates a new instance of AuthsomeMode
*
* @param {string} userId A user's UUID
* @param {string} operation The operation you're authorizing for
* @param {any} object The object of authorization
* @param {any} context Context for authorization, e.g. database access
* @returns {string}
*/
constructor(userId, operation, object, context) {
this.userId = userId
this.operation = AuthsomeMode.mapOperation(operation)
this.object = object
this.context = context
}
/**
* Maps operations from HTTP verbs to semantic verbs
*
* @param {any} operation
* @returns {string}
*/
static mapOperation(operation) {
const operationMap = {
GET: 'read',
POST: 'create',
PATCH: 'update',
DELETE: 'delete',
}
return operationMap[operation] ? operationMap[operation] : operation
}
async isTeamMember(teamType, object) {
let membershipCondition
if (object) {
membershipCondition = team =>
team.teamType === teamType &&
team.object &&
team.object.id === object.id
} else {
membershipCondition = team => team.teamType === teamType
}
const memberships = await Promise.all(
this.user.teams.map(async teamId => {
const teamFound = await this.context.models.Team.find(teamId)
if (teamFound) {
return membershipCondition(teamFound)
}
return false
}),
)
return memberships.includes(true)
}
isAuthor(object) {
return this.isTeamMember('author', object)
}
async findCollectionByObject(object) {
const { type, id, book, object: collection } = object
let collectionId
switch (type) {
case 'fragment':
collectionId = book
break
case 'team':
collectionId = collection.id
break
default:
collectionId = id
break
}
if (id) {
return this.context.models.Collection.find(collectionId)
}
return undefined
}
async canRead() {
this.user = await this.context.models.User.find(this.userId)
// const collection = await this.findCollectionByObject(this.object)
// const permission = await this.isAuthor(collection)
return true
}
async canListCollections() {
this.user = await this.context.models.User.find(this.userId)
return {
filter: async collections => {
const filteredCollections = await Promise.all(
collections.map(async collection => {
const condition = await this.isAuthor(collection)
return condition ? collection : undefined
}, this),
)
return filteredCollections.filter(collection => collection)
},
}
}
async canReadUser() {
this.user = await this.context.models.User.find(this.userId)
if (this.user.id === this.object.id) {
return true
}
return {
filter: user =>
pickBy(user, (_, key) => ['id', 'username', 'type'].includes(key)),
}
}
async canListTeams() {
this.user = await this.context.models.User.find(this.userId)
return {
filter: async teams => {
const filteredTeams = await Promise.all(
teams.map(async team => {
const condition = this.belongsToTeam(team.id)
return condition ? team : undefined
}, this),
)
return filteredTeams.filter(team => team)
},
}
}
belongsToTeam(teamId) {
return this.user.teams.includes(teamId)
}
async canReadTeam() {
this.user = await this.context.models.User.find(this.userId)
return true
}
async canCreateTeam() {
this.user = await this.context.models.User.find(this.userId)
return true
}
async canUpdateTeam() {
this.user = await this.context.models.User.find(this.userId)
return true
}
async canCreateCollection() {
this.user = await this.context.models.User.find(this.userId)
return true
}
}
module.exports = {
before: async (userId, operation, object, context) => {
const user = await context.models.User.find(userId)
return user && user.admin
},
GET: (userId, operation, object, context) => {
// const mode = new AuthsomeMode(userId, operation, object, context)
// GET /api/collections
if (object && object.path === '/collections') {
return true
}
// GET /api/collection
if (object && object.type === 'collection') {
return true
}
// GET /api/collections/:collectionId/fragments
if (object && object.path === '/fragments') {
return true
}
// GET /api/collections/:collectionId/fragments/:fragmentId
if (object && object.type === 'fragment') {
return true
}
// GET /api/users
if (object && object.path === '/users') {
return true
}
// // GET /api/teams
if (object && object.path === '/teams') {
return true
}
// // GET /api/team
if (object && object.type === 'team') {
return true
}
// // GET /api/user
if (object && object.type === 'user') {
return true
}
return false
},
POST: (userId, operation, object, context) => {
const mode = new AuthsomeMode(userId, operation, object, context)
// POST /api/collections
if (object && object.path === '/collections') {
return mode.canCreateCollection()
}
// POST /api/users
if (object && object.path === '/users') {
return true
}
// POST /api/fragments
if (object && object.path === '/collections/:collectionId/fragments') {
return true
}
// POST /api/teams
if (object && object.path === '/teams') {
return true
}
return false
},
PATCH: (userId, operation, object, context) => {
// const mode = new AuthsomeMode(userId, operation, object, context)
// PATCH /api/collections/:id
let data
if (object) {
if (object.current) {
data = object.current
} else {
data = object
}
} else {
return false
}
if (data.type === 'collection') {
return true
}
// PATCH /api/fragments/:id
if (data.type === 'fragment') {
return true
}
// PATCH /api/teams/:id
if (data.current.type === 'team') {
return true
}
return false
},
DELETE: (userId, operation, object, context) => {
// const mode = new AuthsomeMode(userId, operation, object, context)
// DELETE /api/collections/:id
if (object && object.type === 'collection') {
return true
}
// DELETE /api/fragments/:id
if (object && object.type === 'fragment') {
return true
}
// DELETE /api/teams/:id
if (object && object.type === 'team') {
return true
}
return false
},
}
......@@ -16,19 +16,17 @@ module.exports = {
API_ENDPOINT: '/api',
theme: 'PepperTheme',
'login-redirect': '/manage/posts',
'redux-log': true,
'redux-log': false,
},
authsome: {
// this should be either an npm package or an absolute path, not a relative path
mode: 'authsome/src/modes/blog',
mode: path.resolve(__dirname, './authsome-mode.js'),
teams: {
teamContributors: {
name: 'Contributors',
permissions: 'POST',
seniorUser: {
name: 'Senior User',
},
teamCoauthors: {
name: 'Coauthors',
permissions: 'PATCH',
simpleUser: {
name: 'Simple User',
},
},
},
......
......@@ -2,6 +2,9 @@ const { deferConfig } = require('config/defer')
module.exports = {
'pubsweet-server': {
db: {
database: 'starter',
},
baseUrl: deferConfig(
cfg => `http://localhost:${cfg['pubsweet-server'].port}`,
),
......
{
"pubsweet-server": {
"secret": "hello"
}
}
{
"pubsweet-server": {
"secret": "hello"
}
}
const logger = require('winston')
const { deferConfig } = require('config/defer')
module.exports = {
'pubsweet-server': {
db: { database: 'test' },
logger,
port: 4000,
baseUrl: deferConfig(
cfg => `http://localhost:${cfg['pubsweet-server'].port}`,
),
baseUrl: 'http://localhost:4000',
secret: 'test',
},
mailer: {
......
......@@ -10,6 +10,7 @@ module.exports = {
published_at: Joi.string(),
source: Joi.any(),
presentation: Joi.string(),
authors: Joi.array().items(Joi.string()),
},
collection: {
title: Joi.string(),
......
......@@ -3,7 +3,7 @@ services:
web:
image: pubsweet/pubsweet:base
command: sh -c "yarn && yarn server"
command: sh -c "yarn && yarn seed && yarn server"
ports:
- ${PORT:-3000}:3000
volumes:
......@@ -19,5 +19,7 @@ services:
- 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
......@@ -2,36 +2,32 @@
"name": "@pubsweet/starter",
"version": "1.0.0-alpha.1",
"description": "A new pubsweet app",
"files": [
"app",
"config",
"static",
"webpack"
],
"files": ["app", "config", "static", "webpack"],
"main": "app.js",
"dependencies": {
"@pubsweet/db-manager": "^1.0.0",
"@pubsweet/default-theme": "^0.2.0",
"@pubsweet/logger": "^0.2.2",
"@pubsweet/ui": "^3.0.0",
"authsome": "^0.0.9",
"pubsweet": "^2.0.0",
"pubsweet-client": "^2.1.1",
"pubsweet-component-blog": "^1.0.1",
"pubsweet-component-form-group": "1.0.2",
"@pubsweet/coko-theme": "^4.1.0",
"@pubsweet/db-manager": "^2.0.0",
"@pubsweet/default-theme": "3.0.0",
"@pubsweet/logger": "^0.2.6",
"@pubsweet/ui": "^8.6.2",
"authsome": "^0.1.0",
"pubsweet": "^3.0.0",
"pubsweet-client": "^5.0.0",
"pubsweet-component-blog": "^1.0.5",
"pubsweet-component-form-group": "1.1.20",
"pubsweet-component-html": "^0.2.5",
"pubsweet-component-login": "^1.0.1",
"pubsweet-component-manage": "^0.2.3",
"pubsweet-component-login": "1.1.16",
"pubsweet-component-manage": "^1.0.0",
"pubsweet-component-medium-draft": "^0.2.3",
"pubsweet-component-navigation": "^1.0.1",
"pubsweet-component-password-reset-backend": "^1.0.0",
"pubsweet-component-password-reset-frontend": "^1.0.1",
"pubsweet-component-password-reset-frontend": "^2.0.14",
"pubsweet-component-pepper-theme": "^0.0.5",
"pubsweet-component-posts-manager": "1.0.2",
"pubsweet-component-signup": "^1.0.1",
"pubsweet-component-teams-manager": "^1.0.1",
"pubsweet-component-users-manager": "^1.0.1",
"pubsweet-server": "^7.2.0",
"pubsweet-component-posts-manager": "1.0.35",
"pubsweet-component-signup": "^1.0.30",
"pubsweet-component-teams-manager": "^1.1.22",
"pubsweet-component-users-manager": "^2.0.1",
"pubsweet-server": "^9.0.0",
"pubsweet-theme-plugin": "^0.0.3",
"react-router-redux": "next"
},
......@@ -71,6 +67,7 @@
"json-loader": "^0.5.4",
"lint-staged": "^6.1.0",
"node-sass": "^4.5.2",
"objection": "^1.3.0",
"prettier": "^1.10.2",
"react-hot-loader": "^4.1.2",
"regenerator-runtime": "^0.11.0",
......@@ -79,8 +76,8 @@
"smtp-server": "^3.3.0",
"string-replace-loader": "^1.2.0",
"style-loader": "^0.21.0",
"testcafe": "^0.20.5",
"testcafe-react-selectors": "^2.0.0",
"testcafe": "^0.22.0",
"testcafe-react-selectors": "^3.0.0",
"url-loader": "^0.6.2",
"webpack": "^3.11.0",
"webpack-hot-middleware": "^2.22.1"
......@@ -91,9 +88,10 @@
"server": "pubsweet server",
"start:services": "docker-compose up db",
"setupdb": "pubsweet setupdb",
"create:collection": "node scripts/add-collection.js",
"seed": "node scripts/seed.js",
"precommit": "lint-staged",
"test": "NODE_ENV=test NODE_PRESERVE_SYMLINKS=1 testcafe chrome 'test/**/*.test.js'"
"test":
"NODE_ENV=test NODE_PRESERVE_SYMLINKS=1 testcafe chrome 'test/**/*.test.js'"
},
"repository": "https://gitlab.coko.foundation/pubsweet/pubsweet-starter",
"license": "MIT"
......
#!/usr/bin/env node
const logger = require('@pubsweet/logger')
const { addCollection } = require('@pubsweet/db-manager')
const [, , title] = process.argv
if (!title) {
logger.error(
`Please specify a title, e.g. npm run create:collection -- "Some title"`,
)
process.exit()
}
addCollection({ title }).catch(err => logger.error(err))
#!/usr/bin/env node
const logger = require('@pubsweet/logger')
const { Collection, Fragment, User } = require('pubsweet-server/src/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()
const fragment1 = await new Fragment({
title: 'A great paper',
authors: ['Yannis Barlas', 'Adam Hyde'],
owners: [user.id],
}).save()
const fragment2 = await new Fragment({
title: 'A magnificent paper',
authors: ['Alex Theg', 'Ana Ellis'],
owners: [user.id],
}).save()
const fragment3 = await new Fragment({
title: 'A superb paper',
authors: ['Charlie Rutter', 'Carly Strauss'],
owners: [user.id],
}).save()
const fragment4 = await new Fragment({
title: 'A fantastic paper',
authors: ['Alisson Zulowski', 'John Chodaki'],
owners: [user.id],
}).save()
// const fragment2 = await new Fragment({
// title: 'My second post',
// owners: [user.id],
// }).save()
collection.addFragment(fragment1)
collection.addFragment(fragment2)
collection.addFragment(fragment3)
collection.addFragment(fragment4)
await collection.save()
logger.info('Seeding complete.')
}
seed()
......@@ -34,7 +34,7 @@ test('Manage posts journey', async t => {
.typeText(managePosts.newPostInput, postTitle)
.click(managePosts.newPostButton)
.expect(managePosts.postTitle(0).innerText)
.eql(postTitle)
.match(new RegExp(`^${postTitle}`))
// publish it
await t
......@@ -98,7 +98,7 @@ test('Password reset journey', async t => {
// extract reset URL from email content
const mail = await mailHelper.nextEmail()
console.log(mail)
const matchResult = mail
.replace(/=3D/g, '=')
.replace(/=\r?\n/g, '')
......