Starting from Monday (10th of May 2021), 2 Factor Authentication enforcement will be enabled. If 2FA is not configured within 48h, you will not be able to perform any action on GitLab until 2FA is configured. Please click here to learn how to do that.

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

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: '' } },