Commit 1515afcd authored by Nikos Marinos's avatar Nikos Marinos

taken changes from shared-model branch

parent fd8dc7d1
Pipeline #10336 failed with stages
in 3 minutes and 7 seconds
......@@ -2,10 +2,11 @@ import React from 'react'
import { withProps } from 'recompose'
import { Route, Switch } from 'react-router-dom'
import { AuthenticatedComponent } from 'pubsweet-client'
import Login from 'pubsweet-component-login/LoginContainer'
import Signup from 'pubsweet-component-signup/SignupContainer'
import PasswordReset from 'pubsweet-component-password-reset-frontend/PasswordReset'
import UsersManager from 'pubsweet-component-users-manager/src/UsersManagerContainer'
import Login from './components/login/graphql/LoginContainer'
// import {
// FindReviewersPage,
// AuthorPage as FindReviewersAuthorPage,
......@@ -18,6 +19,8 @@ import ReviewersPage from 'pubsweet-component-xpub-review/src/components/Reviewe
import ReviewPage from 'pubsweet-component-xpub-review/src/components/ReviewPage'
import DecisionPage from 'pubsweet-component-xpub-review/src/components/DecisionPage'
*/
import UsersManager from 'pubsweet-component-users-manager/src/UsersManagerContainer'
import DashboardPage from './components/DashboardPage'
import CreatePage from './components/CreatePage'
import CreateSetupPage from './components/CreateSetupPage'
......
......@@ -177,4 +177,9 @@ module.exports = {
login: {
enableMock: false,
},
organization: {
europepmc_plus: {
name: 'Europe PMC Plus',
},
},
}
......@@ -25,7 +25,6 @@ services:
volumes:
- postgres-volume:/var/lib/postgresql/data
- ./scripts/test.sql:/docker-entrypoint-initdb.d/test.sql
- ./scripts/init.sql:/docker-entrypoint-initdb.d/init.sql
- ./scripts/shared-data-model.sql:/docker-entrypoint-initdb.d/shared-data-model.sql
# pgadmin:
......@@ -65,6 +64,7 @@ services:
ports:
- "21:21"
- "30000-30009:30000-30009"
minio:
image: minio/minio:RELEASE.2018-09-12T18-49-56Z
ports:
......
{
"README_schema" : "Specifies how to load the GraphQL schema that completion, error highlighting, and documentation is based on in the IDE",
"schema": {
"README_file" : "Remove 'file' to use request url below. A relative or absolute path to the JSON from a schema introspection query, e.g. '{ data: ... }' or a .graphql/.graphqls file describing the schema using GraphQL Schema Language. Changes to the file are watched.",
"README_request" : "To request the schema from a url instead, remove the 'file' JSON property above (and optionally delete the default graphql.schema.json file).",
"request": {
"url" : "http://localhost:3000/graphql",
"method" : "POST",
"README_postIntrospectionQuery" : "Whether to POST an introspectionQuery to the url. If the url always returns the schema JSON, set to false and consider using GET",
"postIntrospectionQuery" : true,
"README_options" : "See the 'Options' section at https://github.com/then/then-request",
"options" : {
"headers": {
"user-agent" : "JS GraphQL"
}
}
}
},
"README_endpoints": "A list of GraphQL endpoints that can be queried from '.graphql' files in the IDE",
"endpoints" : [
{
"name": "Default (http://localhost:3000/graphql)",
"url": "http://localhost:3000/graphql",
"options" : {
"headers": {
"user-agent" : "JS GraphQL"
}
}
}
]
}
\ No newline at end of file
......@@ -15,6 +15,9 @@ module.exports = {
migrations: {
directory: path.join(__dirname, '/server/xpub-model/migrations'),
},
seeds: {
directory: path.join(__dirname, '/server/xpub-model/seeds'),
},
},
staging: {
......
......@@ -10,14 +10,14 @@ $$ language 'plpgsql';
-------------DB Schema-------------
CREATE TABLE organization (
id UUID PRIMARY KEY,
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
created TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT current_timestamp,
updated TIMESTAMP WITH TIME ZONE,
name TEXT NOT NULL
);
CREATE TABLE journal (
id UUID PRIMARY KEY,
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
-- organization_id UUID NOT NULL REFERENCES organization,
created TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT current_timestamp,
updated TIMESTAMP WITH TIME ZONE,
......@@ -25,12 +25,15 @@ CREATE TABLE journal (
"meta,publisher_name" TEXT
);
CREATE INDEX journalTitle_idx ON journal (journal_title);
CREATE TABLE manuscript (
id UUID PRIMARY KEY,
id SERIAL PRIMARY KEY, -- emsid
journal_id UUID REFERENCES journal,
organization_id UUID NOT NULL REFERENCES organization,
created TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT current_timestamp,
updated TIMESTAMP WITH TIME ZONE,
deleted TIMESTAMP WITH TIME ZONE,
-- points to "previous" (i.e. older) version of manuscript
-- first version has previous_version = null
-- id of current version does not change
......@@ -50,11 +53,13 @@ CREATE TABLE manuscript (
pdf_deposit_state TEXT
);
ALTER SEQUENCE manuscript_id_seq RESTART WITH 80000;
CREATE INDEX pdfState_idx ON manuscript (pdf_deposit_state);
CREATE TABLE file (
id UUID PRIMARY KEY,
manuscript_id UUID NOT NULL REFERENCES manuscript,
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
manuscript_id SERIAL NOT NULL REFERENCES manuscript,
created TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT current_timestamp,
updated TIMESTAMP WITH TIME ZONE,
deleted TIMESTAMP WITH TIME ZONE,
......@@ -68,35 +73,36 @@ CREATE TABLE file (
-- user is a reserved word so we use users instead
CREATE TABLE users (
id UUID PRIMARY KEY,
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
created TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT current_timestamp,
updated TIMESTAMP WITH TIME ZONE,
default_identity TEXT
);
CREATE TABLE review (
id UUID PRIMARY KEY,
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
created TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT current_timestamp,
updated TIMESTAMP WITH TIME ZONE,
deleted TIMESTAMP WITH TIME ZONE,
comments JSONB[],
recommendation TEXT,
open BOOLEAN,
user_id UUID NOT NULL REFERENCES users,
manuscript_id UUID NOT NULL REFERENCES manuscript
manuscript_id SERIAL NOT NULL REFERENCES manuscript
);
CREATE TABLE audit_log (
id UUID PRIMARY KEY,
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
created TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT current_timestamp,
user_id UUID REFERENCES users,
action TEXT NOT NULL,
changes JSONB[],
object_id UUID,
object_type TEXT,
manuscript_id UUID NOT NULL REFERENCES manuscript
manuscript_id SERIAL NOT NULL REFERENCES manuscript
);
CREATE TABLE team (
id UUID,
role TEXT PRIMARY KEY,
created TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT current_timestamp,
updated TIMESTAMP WITH TIME ZONE,
......@@ -108,7 +114,7 @@ CREATE TABLE manuscript_team_users (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
created TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT current_timestamp,
updated TIMESTAMP WITH TIME ZONE,
manuscript_id UUID REFERENCES manuscript,
manuscript_id SERIAL REFERENCES manuscript,
user_id UUID NOT NULL REFERENCES users,
team_role TEXT NOT NULL REFERENCES team(role),
UNIQUE (manuscript_id, user_id, team_role)
......@@ -117,7 +123,7 @@ CREATE TABLE manuscript_team_users (
CREATE UNIQUE INDEX mtu_m_uni_idx ON manuscript_team_users (user_id, team_role) WHERE manuscript_id IS NULL;
CREATE TABLE identity (
id UUID PRIMARY KEY,
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
user_id UUID NOT NULL REFERENCES users,
created TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT current_timestamp,
updated TIMESTAMP WITH TIME ZONE,
......@@ -148,7 +154,7 @@ ALTER TABLE manuscript
CREATE TABLE annotation
(
id uuid DEFAULT uuid_generate_v4 () NOT NULL,
id uuid DEFAULT uuid_generate_v4() NOT NULL,
quote varchar,
comment varchar,
ranges json,
......
......@@ -6,7 +6,7 @@ const STATUS = require('http-status-codes')
const authBearer = passport.authenticate('bearer', { session: false })
const retrieveNewEmsId = async () => {
const { rows } = await db.query(`SELECT nextval('manuscript_id_serial')`)
const { rows } = await db.query(`SELECT nextval('manuscript_id_seq')`)
if (rows && rows.length === 1 && rows[0].nextval) {
return rows[0].nextval
......
......@@ -75,6 +75,50 @@ function getFile(url, dest, cb) {
})
}
function uploadToMinio(filename, manuscript, filePath, item_size, item) {
minioClient.uploadFile(
filename,
`${manuscript.id}.pdf`,
'application/pdf',
filePath,
(error, etag) => {
if (error) {
fs.unlink(filePath, () => {
manuscript.pdfDepositState = 'RESULT_FAILED_TO_BE_RETRIEVED'
new Manuscript(manuscript).save()
})
logger.error(error)
} else {
logger.info('PDF file uploaded to minio')
fs.unlink(filePath, () => {
const pdf4print = 'pdf4print'
// delete it if already exists
manuscript.files = manuscript.files.filter(
file => file.type !== pdf4print,
)
const pdfFile = {
manuscriptId: manuscript.id,
url: `${pubsweetServer}/download/${filename}`,
size: item_size,
filename: item,
type: pdf4print,
}
manuscript.files.push(pdfFile)
manuscript.pdfDepositState = 'RESULT_RETRIEVED'
new Manuscript(manuscript).save().then(() => {
logger.info(
`PDF retrieved and saved successfully for manuscript ${
manuscript.id
}`,
)
})
})
}
},
)
}
function processDeposit(manuscript, deposit) {
if (deposit.state === 'RESULT_IS_READY') {
logger.info(`PDF conversion is ready: ${manuscript.id}. Fetching...`)
......@@ -90,47 +134,7 @@ function processDeposit(manuscript, deposit) {
new Manuscript(manuscript).save()
logger.err(err)
} else {
minioClient.uploadFile(
filename,
`${manuscript.id}.pdf`,
'application/pdf',
filePath,
(error, etag) => {
if (error) {
fs.unlink(filePath, () => {
manuscript.pdfDepositState = 'RESULT_FAILED_TO_BE_RETRIEVED'
new Manuscript(manuscript).save()
})
logger.err(error)
} else {
logger.info('PDF file uploaded to minio')
fs.unlink(filePath, () => {
const pdf4print = 'pdf4print'
// delete it if already exists
manuscript.files = manuscript.files.filter(
file => file.type !== pdf4print,
)
const pdfFile = {
manuscriptId: manuscript.id,
url: `${pubsweetServer}/download/${filename}`,
size: item_size,
filename: item,
type: pdf4print,
}
manuscript.files.push(pdfFile)
manuscript.pdfDepositState = 'RESULT_RETRIEVED'
new Manuscript(manuscript).save().then(() => {
logger.info(
`PDF retrieved and saved successfully for manuscript ${
manuscript.id
}`,
)
})
})
}
},
)
uploadToMinio(filename, manuscript, filePath, item_size, item)
}
})
} else {
......
......@@ -2,7 +2,6 @@
const Journal = require('../xpub-model/entities/journal/data-access')
// const User = require('../xpub-model/entities/user/data-access')
const uuidv4 = require('uuid/v4')
const seed = () => {
/*
......@@ -33,7 +32,6 @@ const seed = () => {
journal.manuscripts.push(emptyManuscript)
const id = uuidv4()
const emptyFile = {
// manuscriptId: id,
url: 'http://staging.europepmc.org/docs/converted.nxml',
......
......@@ -28,6 +28,7 @@ class File extends EpmcBaseModel {
size: { type: 'int' },
url: { type: 'string' },
label: { type: 'string' },
deleted: { type: 'timestamp' },
},
}
}
......@@ -46,6 +47,12 @@ class File extends EpmcBaseModel {
}
}
static async deleteByManuscriptId(id, trx) {
await File.query(trx)
.update({ deleted: new Date().toISOString() })
.where('manuscript_id', id)
}
static async selectById(id) {
const rows = await runQuery(
buildQuery
......
......@@ -40,8 +40,14 @@ class Identity extends EpmcBaseModel {
}
}
static hashPassword(password) {
return bcrypt.hash(password, BCRYPT_COST)
static async hashPassword(password) {
const salt = await bcrypt.genSalt(
process.env.BCRYPT_SALT_ROUNDS
? await parseInt(process.env.BCRYPT_SALT_ROUNDS, 10)
: BCRYPT_COST,
)
const hash = await bcrypt.hash(password, salt)
return hash
}
}
......
const { Model } = require('objection')
const uuid = require('uuid')
const EpmcBaseModel = require('../epmc-base-model')
const { rowToEntity, entityToRow, buildQuery, runQuery } = require('../util')
const columnNames = [
'status',
'organization_id',
'meta,title',
'meta,article_type',
'meta,article_ids',
'meta,abstract',
'meta,subjects',
'meta,notes',
// 'previously_discussed',
// 'previously_submitted',
// 'cosubmission',
// 'related_manuscripts',
// 'suggestions_conflict',
// 'cover_letter',
// 'opposed_senior_editors_reason',
// 'opposed_reviewing_editors_reason',
// 'opposed_reviewers_reason',
// 'submitter_signature',
// 'disclosure_consent',
// 'qc_issues',
'created_by',
'formState',
'pdf_deposit_id',
'pdf_deposit_state',
]
const joinSelect = buildQuery
.select(
'manuscript.*',
buildQuery.raw(
"COALESCE(jsonb_agg(DISTINCT team.*) FILTER (WHERE team.id IS NOT NULL), '[]') AS teams",
),
buildQuery.raw(
"COALESCE(jsonb_agg(DISTINCT file.*) FILTER (WHERE file.id IS NOT NULL), '[]') AS files",
),
)
.from('manuscript')
.leftJoin('team', 'manuscript.id', 'team.object_id')
.leftJoin('file', 'manuscript.id', 'file.manuscript_id')
.groupBy('manuscript.id')
class Manuscript extends EpmcBaseModel {
static get tableName() {
......@@ -53,11 +9,12 @@ class Manuscript extends EpmcBaseModel {
static get schema() {
return {
properties: {
id: { type: 'uuid' },
id: { type: 'int' },
organizationId: { type: 'uuid' },
journalId: { type: 'uuid' },
created: { type: 'timestamp' },
updated: { type: 'timestamp' },
deleted: { type: 'timestamp' },
createdBy: { type: ['string', 'null'] },
previousVersion: { type: 'uuid' },
status: { type: ['string', 'null'] },
......@@ -85,7 +42,16 @@ class Manuscript extends EpmcBaseModel {
static get relationMappings() {
const File = require('../file/data-access')
const Journal = require('../journal/data-access')
const Review = require('../review/data-access')
return {
reviews: {
relation: Model.HasManyRelation,
modelClass: Review,
join: {
from: 'manuscript.id',
to: 'review.manuscriptId',
},
},
files: {
relation: Model.HasManyRelation,
modelClass: File,
......@@ -106,25 +72,17 @@ class Manuscript extends EpmcBaseModel {
}
static async selectById(id) {
const query = joinSelect.clone().where({ 'manuscript.id': id })
const rows = await runQuery(query)
if (!rows.length) {
throw new Error('Manuscript not found')
}
return rowToEntity(rows[0])
const manuscripts = await Manuscript.query()
.where('id', id)
.eager()
return manuscripts[0]
}
static async selectByIdAndUser(id, user) {
const query = joinSelect
.clone()
.where({ 'manuscript.id': id, 'manuscript.created_by': user })
const rows = await runQuery(query)
if (!rows.length) {
throw new Error('Manuscript not found')
}
return rowToEntity(rows[0])
const manuscripts = await Manuscript.query()
.where('id', id)
.andWhere('created_by', user)
return manuscripts[0]
}
static async selectByStatus(status, user) {
......@@ -150,21 +108,18 @@ class Manuscript extends EpmcBaseModel {
}
static async selectAll(user) {
const query = joinSelect
.clone()
.where({ 'manuscript.created_by': user })
.orderBy('manuscript.created', 'desc')
const rows = await runQuery(query)
return rows.map(rowToEntity)
const manuscripts = await Manuscript.query()
.where('created_by', user)
.eager()
return manuscripts
}
static async insert(manuscript) {
const row = entityToRow(manuscript, columnNames)
row.id = uuid.v4()
const query = buildQuery.insert(row).into('manuscript')
await runQuery(query)
return row.id
const { id } = await Manuscript.query().insertGraph(manuscript)
const manuscriptInserted = await Manuscript.query()
.where('id', id)
.eager()
return manuscriptInserted[0]
}
static update(manuscript) {
......@@ -176,12 +131,10 @@ class Manuscript extends EpmcBaseModel {
// return runQuery(query)
}
static delete(id) {
const query = buildQuery
.delete()
.from('manuscript')
.where({ id })
return runQuery(query)
static async delete(id, trx) {
await Manuscript.query(trx).patchAndFetchById(id, {
deleted: new Date().toISOString(),
})
}
}
......
......@@ -2,43 +2,8 @@
* this should be kept in sync with the schema
*/
const emptyManuscript = {
meta: {
title: '',
articleType: '',
subjects: [],
},
opposedSeniorEditorsReason: '',
opposedReviewingEditorsReason: '',
opposedReviewersReason: '',
suggestionsConflict: false,
files: [],
coverLetter: `
<p><strong>How will your work make others in the field think differently and move the field forward?</strong></p>
<p></p>
<p><strong>How does your work relate to the current literature on the topic?</strong></p>
<p></p>
<p><strong>Who do you consider to be the most relevant audience for this work?</strong></p>
<p></p>
<p><strong>Have you made clear in the letter what the work has and has not achieved?</strong></p>
<p></p>
`,
status: 'INITIAL',
createdBy: '',
previouslyDiscussed: null,
previouslySubmitted: [],
cosubmission: [],
submitterSignature: '',
disclosureConsent: false,
teams: [
{
role: 'suggestedReviewer',
teamMembers: [
{ meta: { name: '', email: '' } },
{ meta: { name: '', email: '' } },
{ meta: { name: '', email: '' } },
],
},
],
}
module.exports = emptyManuscript
const { transaction } = require('objection')
const lodash = require('lodash')
const authorization = require('pubsweet-server/src/helpers/authorization')
const logger = require('@pubsweet/logger')
const emptyManuscript = require('./helpers/empty')
const ManuscriptAccess = require('./data-access')
const TeamManager = require('../team')
const FileManager = require('../file')
const FileAccess = require('../file/data-access')
const ReviewAccess = require('../review/data-access')
// const dataAccess = new ManuscriptAccess()
......@@ -32,7 +34,7 @@ const Manuscript = {
MAX_SUGGESTED_REVIEWERS: 6,
MIN_SUGGESTED_REVIEWERS: 3,
all: ManuscriptAccess.selectAll,
all: async user => ManuscriptAccess.selectAll(user),
findById: async (id, userId) => {
const manuscript = await ManuscriptAccess.selectById(id)
await authorization.can(userId, 'read', {
......@@ -47,17 +49,34 @@ const Manuscript = {
findByDepositStatesNull: ManuscriptAccess.selectByPdfDepositStatesNull,
delete: async (id, userId) => {
const manuscript = await ManuscriptAccess.selectById(id, userId)
await Promise.all(manuscript.files.map(file => FileManager.delete(file.id)))
await Promise.all(manuscript.teams.map(team => TeamManager.delete(team.id)))
await ManuscriptAccess.delete(id)
let trx
try {
trx = await transaction.start(ManuscriptAccess.knex())
const manuscript = await ManuscriptAccess.selectByIdAndUser(id, userId)
if (manuscript) {
await ManuscriptAccess.delete(id, trx)
await FileAccess.deleteByManuscriptId(id, trx)
await ReviewAccess.deleteByManuscriptId(id, trx)
await trx.commit()
return true
}
return false
} catch (error) {
if (trx) {
await trx.rollback()
}
logger.error('Nothing was deleted')
logger.error(error)
throw error
}
},
new: (params = {}) => lodash.merge({}, emptyManuscript, params),
save: async (manuscript, userId) => {
// TODO wrap these queries in a transaction
let { id } = manuscript
const { id } = manuscript
let saved
if (id) {
const updated = await ManuscriptAccess.update(manuscript)
if (!updated) {
......@@ -69,26 +88,10 @@ const Manuscript = {
type: 'Manuscript',
status: manuscript.status,
})
id = await ManuscriptAccess.insert(manuscript)
}
if (manuscript.teams) {
await Promise.all(
manuscript.teams.map(team =>
TeamManager.save({ ...team, objectId: id, objectType: 'manuscript' }),
),
)
}
if (manuscript.files) {
await Promise.all(
manuscript.files.map(file =>
FileManager.save({ ...file, manuscriptId: id }),
),
)
saved = await ManuscriptAccess.insert(manuscript)
}
return { ...manuscript, id }
return saved
},
applyInput: (originalManuscript, input) => {
......
......@@ -38,8 +38,7 @@ class Organization extends EpmcBaseModel {
}
static async selectAll() {
const rows = await runQuery(buildQuery.select().from('organization'))
return rows.map(rowToEntity)
return Organization.query()
}
static async insert(organization) {
......