Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
index.js 11.81 KiB
const { transaction } = require('objection')
const config = require('config')
const lodash = require('lodash')
const rfr = require('rfr')
const authorization = require('pubsweet-server/src/helpers/authorization')
const logger = require('@pubsweet/logger')
const EmailValidator = require('email-validator')
const ManuscriptAccess = require('./data-access')
const FileAccess = require('../file/data-access')
const NoteAccess = require('../note/data-access')
const ReviewAccess = require('../review/data-access')
const UserAccess = require('../user/data-access')
const Team = require('../team/data-access')
const { dManuscriptUpdate, gManuscript } = require('./helpers/transform')

const { userMessage, reviewerEmail, submitterRejectEmail } = rfr('server/email')

const Manuscript = {
  selectActivityById: async id => {
    const manuscript = await ManuscriptAccess.selectActivityById(id)
    const gMan = gManuscript(manuscript)
    return gMan
  },
  all: async user => {
    const Manuscripts = await ManuscriptAccess.selectAll(user)
    return Manuscripts.map(manuscript => gManuscript(manuscript))
  },
  findByStatus: async (query, page, pageSize, user) => {
    const manuscripts = await ManuscriptAccess.selectByStatus(
      query,
      page,
      pageSize,
      user,
    )

    const rtn =
      page === -1
        ? manuscripts.map(manuscript => gManuscript(manuscript))
        : {
            total: manuscripts.total,
            manuscripts: manuscripts.results.map(manuscript =>
              gManuscript(manuscript),
            ),
          }

    return rtn
  },
  findById: async (id, userId) => {
    const manuscript = await ManuscriptAccess.selectById(id, true)
    await authorization.can(userId, 'read', {
      id,
      type: 'Manuscript',
      status: manuscript.status,
    })
    return gManuscript(manuscript)
  },
  findByArticleId: async (id, userId) => {
    let manuscript = null
    if (id.toUpperCase().startsWith('EMS')) {
      manuscript = await ManuscriptAccess.selectById(id)
    } else {
      const manuscripts = await ManuscriptAccess.searchArticleIds(id, userId)
      manuscript = manuscripts.pop()
    }
    return {
      manuscript,
      errors: manuscript
        ? null
        : [{ message: `No manuscript found with ID: ${id}` }],
    }
  },
  checkDuplicates: async (id, pmid, title, user) => {
    let manuscripts = []
    if (pmid) {
      const idMatches = await ManuscriptAccess.searchArticleIds(pmid, user)
      manuscripts = manuscripts.concat(
        idMatches.reduce((other, each) => {
          if (each.id !== id) {
            other.push(each)
          }
          return other
        }, []),
      )
    }
    const titleMatches = await ManuscriptAccess.searchByTitleOrLastname(
      title,
      0,
      10,
      user,
    )
    manuscripts = manuscripts.concat(
      titleMatches.results.reduce((other, each) => {
        if (each.id !== id && !manuscripts.some(m => m.id === each.id)) {
          other.push(each)
        }
        return other
      }, []),
    )
    return manuscripts
  },
  countByStatus: async () => {
    const { states } = config
    const counts = await states.map(async type => {
      const count = await ManuscriptAccess.countByStatus(type)
      return { type, count }
    })
    return counts
  },
  search: async (query, page, pageSize, user) => {
    let manuscripts
    if (EmailValidator.validate(query)) {
      manuscripts = await ManuscriptAccess.searchByEmail(
        query,
        page,
        pageSize,
        user,
      )
      logger.debug('manuscripts: ', manuscripts)
      return {
        total: manuscripts.total,
        manuscripts: manuscripts.results.map(manuscript =>
          gManuscript(manuscript),
        ),
      }
    }
    manuscripts = await ManuscriptAccess.searchByTitleOrLastname(
      query,
      page,
      pageSize,
      user,
    )
    logger.debug('manuscripts: ', manuscripts)
    return {
      total: manuscripts.total,
      manuscripts: manuscripts.results.map(manuscript =>
        gManuscript(manuscript),
      ),
    }
  },
  findByDepositStates: ManuscriptAccess.selectByPdfDepositStates,
  findByDepositStatesNull: ManuscriptAccess.selectByPdfDepositStatesNull,

  delete: async (id, userId) => {
    let trx
    try {
      trx = await transaction.start(ManuscriptAccess.knex())
      const manuscript = await ManuscriptAccess.selectById(id)
      if (manuscript) {
        await ManuscriptAccess.delete(id, userId, trx)
        await FileAccess.deleteByManuscriptId(id, userId, trx)
        await ReviewAccess.deleteByManuscriptId(id, userId, trx)
        await NoteAccess.deleteByManuscriptId(id, userId, 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
    }
  },

  create: async (userId, organizationId) => {
    await authorization.can(userId, 'create', {
      id: null,
      type: 'Manuscript',
      status: config.manuscript.status.initial,
    })

    let trx
    let savedMan
    try {
      trx = await transaction.start(ManuscriptAccess.knex())
      const manuscript = new ManuscriptAccess({
        organizationId,
        updatedBy: userId,
        status: config.manuscript.status.initial,
      })
      savedMan = await manuscript.saveWithTrx(trx)

      const team = new Team({
        manuscriptId: savedMan.id,
        userId,
        roleName: config.authsome.teams.submitter.name,
        updatedBy: userId,
      })
      await team.saveWithTrx(trx)
      await trx.commit()
    } catch (error) {
      if (trx) {
        await trx.rollback()
      }
      logger.error('Nothing was created: ', error)
      throw error
    }
    return savedMan
  },

  changeClaim: async (manId, userId, unclaim = false) => {
    const originalMan = await ManuscriptAccess.selectById(manId)
    let manuscriptUpdate = {}
    const errors = []
    if (originalMan.claimedBy && unclaim) {
      if (originalMan.claimedBy === userId) {
        manuscriptUpdate = dManuscriptUpdate({ claimedBy: null }, userId)
      } else {
        errors.push({ message: 'You have not claimed this manuscript' })
      }
    } else if (originalMan.claimedBy) {
      errors.push({ message: 'Manuscript already claimed' })
    } else {
      manuscriptUpdate = dManuscriptUpdate({ claimedBy: userId }, userId)
    }
    lodash.assign(originalMan, manuscriptUpdate)
    await originalMan.save()
    const updatedMan = await ManuscriptAccess.selectById(manId, true)
    return {
      manuscript: gManuscript(updatedMan),
      errors,
    }
  },

  update: async (input, userId) => {
    const originalMan = await ManuscriptAccess.selectById(input.id)
    if (!originalMan) {
      throw new Error('Manuscript not found')
    }
    const manuscriptUpdate = dManuscriptUpdate(input, userId)
    lodash.assign(originalMan, manuscriptUpdate)
    await originalMan.save()
    const updatedMan = await ManuscriptAccess.selectById(input.id, true)
    return gManuscript(updatedMan)
  },

  submit: async (input, userId) => {
    const originalMan = await ManuscriptAccess.selectById(input.id)
    const errors = []
    if (!originalMan) {
      errors.push({ message: 'Manuscript not found' })
      return {
        manuscript: null,
        errors,
      }
    }
    if (input.status === 'submitted') {
      const notes = await NoteAccess.selectByManuscriptId(input.id)
      const reviewerNote = notes.find(n => n.notesType === 'selectedReviewer')
      const teams = await Team.selectByManuscriptId(input.id)
      const subTeam = teams.find(t => t.roleName === 'submitter')
      const revTeam = teams.find(t => t.roleName === 'reviewer')
      const submitter = await UserAccess.findById(subTeam.userId)
      let inReview = false
      let reviewer = {}
      let token = ''
      if (reviewerNote) {
        reviewer = JSON.parse(reviewerNote.content)
        token = reviewerNote.id
        if (reviewer.id) {
          let trx
          try {
            trx = await transaction.start(Team.knex())
            const team = new Team({
              manuscriptId: input.id,
              userId: reviewer.id,
              roleName: 'reviewer',
              updatedBy: userId,
            })
            await team.saveWithTrx(trx)
            await NoteAccess.delete(reviewerNote.id, userId)
            if (revTeam) {
              await Team.delete(revTeam.id, userId)
            }
            await trx.commit()
          } catch (error) {
            if (trx) {
              await trx.rollback()
            }
            logger.error('Nothing was created: ', error)
            errors.push(error)
            return {
              manuscript: null,
              errors,
            }
          }
          if (reviewer.id !== submitter.id) {
            inReview = true
          }
        } else {
          inReview = true
        }
      } else if (
        originalMan.status === 'submission-error' &&
        revTeam &&
        revTeam.userId !== submitter.id &&
        userId !== revTeam.userId
      ) {
        const revUser = await UserAccess.findById(revTeam.userId)
        const { id, title, givenNames, surname, identities } = revUser
        const { email } = identities[0]
        inReview = true
        reviewer = {
          id,
          name: {
            title,
            givenNames,
            surname,
          },
          email,
        }
        token = 'correction'
      } else if (!revTeam) {
        errors.push({ message: 'Reviewer must be selected' })
        return {
          manuscript: null,
          errors,
        }
      }
      if (inReview) {
        input.status = 'in-review'
        await reviewerEmail({
          reviewer,
          manInfo: {
            id: input.id,
            title: originalMan['meta,title'],
          },
          submitter,
          token,
        })
      }
    }
    input.claimedBy = null
    try {
      const manuscriptUpdate = dManuscriptUpdate(input, userId)
      lodash.assign(originalMan, manuscriptUpdate)
      await originalMan.save()
    } catch (error) {
      logger.error(error)
      errors.push(error)
    }
    const updatedMan = await ManuscriptAccess.selectById(input.id, true)
    return {
      manuscript: gManuscript(updatedMan),
      errors,
    }
  },
  reject: async (input, userId) => {
    const originalMan = await ManuscriptAccess.selectById(input.manuscriptId)
    const teams = await Team.selectByManuscriptId(input.manuscriptId)
    const subTeam = teams.find(t => t.roleName === 'submitter')
    const revTeam = teams.find(t => t.roleName === 'reviewer')
    const submitter = await UserAccess.findById(subTeam.userId)
    const reviewer = await UserAccess.findById(revTeam.userId)
    const email = JSON.parse(input.content)
    const errors = []
    try {
      await NoteAccess.insert(input, userId)
      const manUpdate = {
        claimedBy: null,
        formState: email.message ? email.message : email,
        status: 'submission-error',
        updatedBy: userId,
      }
      lodash.assign(originalMan, manUpdate)
      await originalMan.save()
    } catch (error) {
      logger.error(error)
      errors.push(error)
      return {
        manuscript: null,
        errors,
      }
    }
    if (email.to) {
      email.to.forEach(async e => {
        const sendTo =
          (submitter.id === e && submitter.identities[0].email) ||
          (reviewer.id === e && reviewer.identities[0].email)
        await userMessage(sendTo, email.subject, email.message)
      })
    } else {
      await submitterRejectEmail({
        reviewer,
        manInfo: {
          id: originalMan.id,
          title: originalMan['meta,title'],
        },
        submitter,
        message: email,
      })
    }
    const updatedMan = await ManuscriptAccess.selectById(originalMan.id, true)
    return {
      manuscript: gManuscript(updatedMan),
      errors,
    }
  },
}

module.exports = Manuscript