diff --git a/server/model-manuscript/src/graphql.js b/server/model-manuscript/src/graphql.js index c65b23affceefb6f9e7689b6a4efc0d080881074..81e51a136d45c495fe6b4071cf22e79ee98543bd 100644 --- a/server/model-manuscript/src/graphql.js +++ b/server/model-manuscript/src/graphql.js @@ -2,6 +2,42 @@ const detailsForURLResolver = require('./detailsForURLResolver') const { ref, raw } = require('objection') +const ManuscriptResolvers = ({ isVersion }) => { + const resolvers = { + submission(parent) { + return JSON.stringify(parent.submission) + }, + async reviews(parent, _, ctx) { + return parent.reviews + ? parent.reviews + : ( + await ctx.models.Manuscript.query().findById(parent.id) + ).$relatedQuery('reviews') + }, + async teams(parent, _, ctx) { + return parent.teams + ? parent.teams + : ( + await ctx.models.Manuscript.query().findById(parent.id) + ).$relatedQuery('teams') + }, + meta(parent) { + return { ...parent.meta, manuscriptId: parent.id } + }, + } + if (!isVersion) { + resolvers.manuscriptVersions = async (parent, _, ctx) => { + if (!parent.manuscriptVersions.length) { + return ctx.models.Manuscript.relatedQuery('manuscriptVersions') + .for(parent.id) + .orderBy('created', 'desc') + } + return parent.manuscriptVersions + } + } + return resolvers +} + const resolvers = { Mutation: { async createManuscript(_, vars, ctx) { @@ -124,6 +160,7 @@ const resolvers = { async updateManuscript(_, { id, input }, ctx) { const data = JSON.parse(input) const manuscript = await ctx.models.Manuscript.query().findById(id) + const update = Object.assign({}, manuscript, data) // We specifically merge submission, as it itself has nested properties // But we don't want to do a deep merge unconditionally, as that prevents @@ -133,9 +170,35 @@ const resolvers = { manuscript.submission, data.submission, ) - // const update.submission = + // if (manuscript.status === 'revise') { + // return manuscript.createNewVersion(update) + // } return ctx.models.Manuscript.query().updateAndFetchById(id, update) }, + + async createNewVersion(_, { id }, ctx) { + const manuscript = await ctx.models.Manuscript.query().findById(id) + return manuscript.createNewVersion() + }, + async submitManuscript(_, { id, input }, ctx) { + const data = JSON.parse(input) + const manuscript = await ctx.models.Manuscript.query().findById(id) + const update = Object.assign({}, manuscript, data) + // We specifically merge submission, as it itself has nested properties + // But we don't want to do a deep merge unconditionally, as that prevents + // any kind of deletion happening. + update.submission = Object.assign( + {}, + manuscript.submission, + data.submission, + ) + + // if (manuscript.status === 'revise') { + // return manuscript.createNewVersion(update) + // } + return ctx.models.Manuscript.query().updateAndFetchById(id, update) + }, + async makeDecision(_, { id, decision }, ctx) { const manuscript = await ctx.models.Manuscript.query().findById(id) manuscript.decision = decision @@ -217,7 +280,9 @@ const resolvers = { const manuscript = await Manuscript.query() .findById(id) - .eager('[teams, channels, reviews.[user, comments], files]') + .withGraphFetched( + '[teams, channels, files, reviews.[user, comments], manuscriptVersions(orderByCreated)]', + ) if (!manuscript.meta) { manuscript.meta = {} @@ -241,7 +306,12 @@ const resolvers = { return manuscript }, async manuscripts(_, { where }, ctx) { - return ctx.models.Manuscript.query().eager('[teams, reviews]') + return ctx.models.Manuscript.query() + .withGraphFetched( + '[teams, reviews, manuscriptVersions(orderByCreated)]', + ) + .where({ parentId: null }) + .orderBy('created', 'desc') }, async publishedManuscripts(_, { offset, limit }, ctx) { const query = ctx.models.Manuscript.query() @@ -262,7 +332,14 @@ const resolvers = { } }, async paginatedManuscripts(_, { sort, offset, limit, filter }, ctx) { - const query = ctx.models.Manuscript.query().eager('submitter') + const query = ctx.models.Manuscript.query() + .where({ parentId: null }) + .withGraphFetched('[submitter, manuscriptVersions(orderByCreated)]') + .modifiers({ + orderByCreated(builder) { + builder.orderBy('created', 'desc') + }, + }) if (filter && filter.status) { query.where({ status: filter.status }) @@ -302,33 +379,8 @@ const resolvers = { // We want submission into to come out as a stringified JSON, so that we don't have to // change our queries if the submission form changes. We still want to store it as JSONB // so that we can easily search through the information within. - Manuscript: { - submission(parent) { - return JSON.stringify(parent.submission) - }, - async reviews(parent, _, ctx) { - return parent.reviews - ? parent.reviews - : ( - await ctx.models.Manuscript.query().findById(parent.id) - ).$relatedQuery('reviews') - }, - async teams(parent, _, ctx) { - return parent.teams - ? parent.teams - : ( - await ctx.models.Manuscript.query().findById(parent.id) - ).$relatedQuery('teams') - }, - meta(parent) { - return { ...parent.meta, manuscriptId: parent.id } - }, - }, - ManuscriptVersion: { - submission(parent) { - return JSON.stringify(parent.submission) - }, - }, + Manuscript: ManuscriptResolvers({ isVersion: false }), + ManuscriptVersion: ManuscriptResolvers({ isVersion: true }), } const typeDefs = ` @@ -371,6 +423,7 @@ const typeDefs = ` extend type Mutation { createManuscript(input: ManuscriptInput): Manuscript! updateManuscript(id: ID!, input: String): Manuscript! + submitManuscript(id: ID!, input: String): Manuscript! makeDecision(id: ID!, decision: String): Manuscript! deleteManuscript(id: ID!): ID! reviewerResponse(currentUserId: ID, action: String, teamId: ID! ): Team @@ -378,6 +431,7 @@ const typeDefs = ` addReviewer(manuscriptId: ID!, userId: ID!): Team removeReviewer(manuscriptId: ID!, userId: ID!): Team publishManuscript(id: ID!): Manuscript + createNewVersion(id: ID!): Manuscript } type Manuscript implements Object { @@ -413,7 +467,9 @@ const typeDefs = ` authors: [Author] meta: ManuscriptMeta submission: String + submitter: User published: DateTime + parentId: ID } input ManuscriptInput { diff --git a/server/model-manuscript/src/manuscript.js b/server/model-manuscript/src/manuscript.js index 38317a3928ebe287a5568a6c236722c87efc08af..cd93e8bdfd7f219934bde011a4efb1174da69685 100644 --- a/server/model-manuscript/src/manuscript.js +++ b/server/model-manuscript/src/manuscript.js @@ -13,48 +13,13 @@ class Manuscript extends BaseModel { this.type = 'Manuscript' } - // static async myManuscripts(myManuscripts) { - // const mainManuscript = {} - // myManuscripts.forEach(manuscript => { - // if (!mainManuscript[manuscript.parentId || manuscript.id]) { - // mainManuscript[manuscript.parentId || manuscript.id] = manuscript - // } else { - // const checkManuscript = - // mainManuscript[manuscript.parentId || manuscript.id] - // // Compare Dates - // const dateCheckManuscript = new Date(checkManuscript.created).getTime() - // const dateManuscript = new Date(manuscript.created).getTime() - // if (dateManuscript >= dateCheckManuscript) { - // mainManuscript[manuscript.parentId || manuscript.id] = manuscript - // } - // } - // }) - - // const latestManuscripts = values(mainManuscript) - // await Promise.all( - // latestManuscripts.map(async manuscript => { - // manuscript.teams = await new Manuscript(manuscript).getTeams() - // manuscript.reviews = await new Manuscript(manuscript).getReviews() - // manuscript.manuscriptVersions = - // (await manuscript.getManuscriptVersions()) || [] - // return manuscript - // }), - // ) - - // return latestManuscripts - // } - - // async getTeams() { - // const { Team } = require('@pubsweet/models') - // const myTeams = await Team.query() - // .where({ - // objectId: this.id, - // objectType: 'Manuscript', - // }) - // .eager('members') - - // return myTeams - // } + static get modifiers() { + return { + orderByCreated(builder) { + builder.orderBy('created', 'desc') + }, + } + } async getReviews() { // TODO: Use relationships @@ -98,61 +63,35 @@ class Manuscript extends BaseModel { } async createNewVersion() { - const { Team, File } = require('@pubsweet/models') - - const manuscriptReviews = (await this.$query().eager('reviews')).reviews - const manuscriptTeams = ( - await this.$query().eager('[teams, teams.members]') - ).teams - const teams = manuscriptTeams.filter( - team => - team.role === 'author' || - team.role === 'seniorEditor' || - team.role === 'handlingEditor', - ) - - const manuscriptFiles = await File.query().where({ - manuscriptId: this.id, + // Copy authors to the new version + const teams = await this.$relatedQuery('teams') + .where({ role: 'author' }) + .withGraphFetched('members') + teams.forEach(t => { + delete t.id + t.members.forEach(tm => delete tm.id) }) - const manuscriptDecision = manuscriptReviews.find( - review => review.isDecision, - ) + // Copy files as well + const files = await this.$relatedQuery('files') + files.forEach(f => delete f.id) - const dataManuscript = await new Manuscript( - omit(cloneDeep(this), ['id', 'created', 'updated', 'decision']), - ) + const newVersion = cloneDeep(this) + newVersion.teams = teams + newVersion.files = files - dataManuscript.status = - manuscriptDecision.recommendation === 'revise' - ? 'revising' - : manuscriptDecision.recommendation - - dataManuscript.parentId = this.parentId || this.id - const newManuscript = await dataManuscript.save() - - if (teams.length > 0) { - // Copy Teams to the new Version - await Promise.all( - teams.map(async team => { - team.manuscriptId = newManuscript.id - team.members = team.members.map(member => omit(member, 'id')) - await new Team(omit(team, ['id'])).saveGraph() - }), - ) + if (this.decision === 'revise') { + newVersion.status = 'revising' } - // Copy Files to the new Version - await Promise.all( - manuscriptFiles.map(async file => { - const newFile = omit(file, ['id']) - newFile.manuscriptId = newManuscript.id - await new File(newFile).save() - return newFile - }), + // All versions should be linked to one parent, original manuscript + newVersion.parentId = this.parentId || this.id + + const manuscript = await Manuscript.query().insertGraphAndFetch( + omit(cloneDeep(newVersion), ['id', 'created', 'updated', 'decision']), ) - return this + return manuscript } static get relationMappings() { @@ -207,6 +146,14 @@ class Manuscript extends BaseModel { to: 'manuscripts.parentId', }, }, + manuscriptVersions: { + relation: BaseModel.HasManyRelation, + modelClass: Manuscript, + join: { + from: 'manuscripts.id', + to: 'manuscripts.parentId', + }, + }, } } diff --git a/server/model-review/src/graphql.js b/server/model-review/src/graphql.js index 8132e4874c53852c0c53c34a780a482c15c671d7..a486fef8f30b109e9d283b8ce4dcf81c71db6b80 100644 --- a/server/model-review/src/graphql.js +++ b/server/model-review/src/graphql.js @@ -51,6 +51,13 @@ const resolvers = { return member.save() }, }, + Review: { + async user(parent, _, ctx) { + return parent.user + ? parent.user + : ctx.models.User.query().findById(parent.userId) + }, + }, ReviewComment: { async files(parent, _, ctx) { return parent.files diff --git a/server/model-team/src/team_member.js b/server/model-team/src/team_member.js index eff8dd732633a4249c9913b8f2ac221dec14dded..3fe2fafd5f6d10711b508183108459a9ab319eb6 100644 --- a/server/model-team/src/team_member.js +++ b/server/model-team/src/team_member.js @@ -42,7 +42,7 @@ class TeamMember extends BaseModel { userId: { type: 'string', format: 'uuid' }, teamId: { type: 'string', format: 'uuid' }, aliasId: { type: ['string', 'null'], format: 'uuid' }, - status: { type: 'string' }, + status: { type: ['string', 'null'] }, }, } } diff --git a/server/model-user/src/graphql.js b/server/model-user/src/graphql.js index 241ca795f8d0622f0da7d0ff144ca53a494ea721..0b2f947a35ef3c09db7dd385a71f3cca3f6ae22e 100644 --- a/server/model-user/src/graphql.js +++ b/server/model-user/src/graphql.js @@ -3,8 +3,15 @@ const { AuthorizationError, ConflictError } = require('@pubsweet/errors') const resolvers = { Query: { - user(_, { id }, ctx) { - return ctx.models.User.query().findById(id) + user(_, { id, username }, ctx) { + if (id) { + return ctx.models.User.query().findById(id) + } else if (username) { + return ctx.models.User.query() + .where({ username }) + .first() + } + return null }, async users(_, vars, ctx) { return ctx.models.User.query()