Skip to content
Snippets Groups Projects
Commit 17424d12 authored by Jure's avatar Jure
Browse files

refactor: serverside improvements to models

parent 58328c0c
No related branches found
No related tags found
No related merge requests found
...@@ -60,8 +60,10 @@ const configureApp = app => { ...@@ -60,8 +60,10 @@ const configureApp = app => {
if (config.has('pubsweet-server.uploads')) { if (config.has('pubsweet-server.uploads')) {
app.use( app.use(
'/uploads', '/static/uploads',
express.static(path.resolve(config.get('pubsweet-server.uploads'))), express.static(
path.join(__dirname, '..', config.get('pubsweet-server.uploads')),
),
) )
} }
// Passport strategies // Passport strategies
......
const crypto = require('crypto')
const { promisify } = require('util')
const fs = require('fs-extra')
const path = require('path')
const config = require('config')
const File = require('./file') const File = require('./file')
const randomBytes = promisify(crypto.randomBytes)
const uploadsPath = config.get('pubsweet-server').uploads
const upload = async file => {
const { stream, filename, encoding } = await file
const raw = await randomBytes(16)
const generatedFilename = raw.toString('hex') + path.extname(filename)
const outPath = path.join(uploadsPath, generatedFilename)
await fs.ensureDir(uploadsPath)
const outStream = fs.createWriteStream(outPath)
stream.pipe(outStream, { encoding })
return new Promise((resolve, reject) => {
outStream.on('finish', () => resolve(outPath))
outStream.on('error', reject)
})
}
const resolvers = { const resolvers = {
Query: {}, Query: {},
Mutation: { Mutation: {
async createFile(_, { id, file }, ctx) { async createFile(_, { file, meta }, ctx) {
const data = await new File(file).save() const path = await upload(file)
meta.url = `/static/${path}`
const data = await new File(meta).save()
return data return data
}, },
}, },
......
const typeDefs = ` const typeDefs = `
extend type Mutation { extend type Mutation {
createFile(file: Upload!): File! # Using a separate variable because the Upload type hides other data
createFile(file: Upload!, meta: FileMetaInput): File!
}
input FileMetaInput {
fileType: String
filename: String
mimeType: String
object: String
objectId: ID!
label: String
size: Int
} }
type File implements Object { type File implements Object {
......
...@@ -143,18 +143,74 @@ const resolvers = { ...@@ -143,18 +143,74 @@ const resolvers = {
}, },
async updateManuscript(_, { id, input }, ctx) { async updateManuscript(_, { id, input }, ctx) {
const data = JSON.parse(input) const data = JSON.parse(input)
const manuscript = await ctx.models.Manuscript.findById(id) const manuscript = await ctx.models.Manuscript.query().findById(id)
const update = merge({}, manuscript, data) const update = merge({}, manuscript, data)
return ctx.models.Manuscript.update(id, update, ctx) return ctx.models.Manuscript.query().updateAndFetchById(id, update)
}, },
async makeDecision(_, { id, decision }, ctx) { async makeDecision(_, { id, decision }, ctx) {
const manuscript = await ctx.models.Manuscript.findById(id) const manuscript = await ctx.models.Manuscript.query().findById(id)
manuscript.decision = decision manuscript.decision = decision
manuscript.status = decision manuscript.status = decision
return manuscript.save() return manuscript.save()
}, },
async addReviewer(_, { manuscriptId, userId }, ctx) {
const manuscript = await ctx.models.Manuscript.query().findById(
manuscriptId,
)
const existingTeam = await manuscript
.$relatedQuery('teams')
.where('role', 'reviewer')
.first()
// Add the reviewer to the existing team of reviewers
if (existingTeam) {
const reviewerExists =
(await existingTeam
.$relatedQuery('users')
.where('users.id', userId)
.resultSize()) > 0
if (!reviewerExists) {
await new ctx.models.TeamMember({
teamId: existingTeam.id,
status: 'invited',
userId,
}).save()
}
return existingTeam.$query().eager('members.[user]')
}
// Create a new team of reviewers if it doesn't exist
const newTeam = await new ctx.models.Team({
objectId: manuscriptId,
objectType: 'Manuscript',
members: [{ status: 'invited', userId }],
role: 'reviewer',
}).saveGraph()
return newTeam
},
async removeReviewer(_, { manuscriptId, userId }, ctx) {
const manuscript = await ctx.models.Manuscript.query().findById(
manuscriptId,
)
const reviewerTeam = await manuscript
.$relatedQuery('teams')
.where('role', 'reviewer')
.first()
await ctx.models.TeamMember.query()
.where({
userId,
teamId: reviewerTeam.id,
})
.delete()
return reviewerTeam.$query().eager('members.[user]')
},
}, },
Query: { Query: {
async manuscript(_, { id }, ctx) { async manuscript(_, { id }, ctx) {
...@@ -277,6 +333,8 @@ const typeDefs = ` ...@@ -277,6 +333,8 @@ const typeDefs = `
deleteManuscript(id: ID!): ID! deleteManuscript(id: ID!): ID!
reviewerResponse(currentUserId: ID, action: String, teamId: ID! ): Team reviewerResponse(currentUserId: ID, action: String, teamId: ID! ): Team
assignTeamEditor(id: ID!, input: String): [Team] assignTeamEditor(id: ID!, input: String): [Team]
addReviewer(manuscriptId: ID!, userId: ID!): Team
removeReviewer(manuscriptId: ID!, userId: ID!): Team
} }
type Manuscript implements Object { type Manuscript implements Object {
......
...@@ -5,11 +5,13 @@ const resolvers = { ...@@ -5,11 +5,13 @@ const resolvers = {
Mutation: { Mutation: {
async updateReview(_, { id, input }, ctx) { async updateReview(_, { id, input }, ctx) {
if (id) { if (id) {
const review = await ctx.connectors.Review.fetchOne(id, ctx) const review = await ctx.models.Review.query().findById(id)
const update = merge({}, review, input) const update = merge({}, review, input)
await ctx.connectors.Review.update(id, update, ctx)
// Load Review // Load Review
const rvw = await new Review(update) const rvw = await ctx.models.Review.query().updateAndFetchById(
id,
update,
)
rvw.comments = await rvw.getComments() rvw.comments = await rvw.getComments()
return rvw return rvw
...@@ -21,6 +23,25 @@ const resolvers = { ...@@ -21,6 +23,25 @@ const resolvers = {
return review return review
}, },
async completeReview(_, { id }, ctx) {
const review = await ctx.models.Review.query().findById(id)
const manuscript = await ctx.models.Manuscript.query().findById(
review.manuscriptId,
)
const team = await manuscript
.$relatedQuery('teams')
.where('role', 'reviewer')
.first()
const member = await team
.$relatedQuery('members')
.where('userId', ctx.user.id)
.first()
member.status = 'completed'
return member.save()
},
}, },
} }
......
const typeDefs = ` const typeDefs = `
extend type Mutation { extend type Mutation {
updateReview(id: ID, input: ReviewInput): Review! updateReview(id: ID, input: ReviewInput): Review!
completeReview(id: ID!): TeamMember
} }
type Review implements Object { type Review implements Object {
id: ID! id: ID!
created: DateTime! created: DateTime!
updated: DateTime updated: DateTime
......
...@@ -98,7 +98,7 @@ const typeDefs = ` ...@@ -98,7 +98,7 @@ const typeDefs = `
id: ID! id: ID!
type: String! type: String!
role: String! role: String!
name: String! name: String
object: TeamObject object: TeamObject
members: [TeamMember!] members: [TeamMember!]
owners: [User] owners: [User]
......
...@@ -43,7 +43,6 @@ class TeamMember extends BaseModel { ...@@ -43,7 +43,6 @@ class TeamMember extends BaseModel {
teamId: { type: 'string', format: 'uuid' }, teamId: { type: 'string', format: 'uuid' },
aliasId: { type: ['string', 'null'], format: 'uuid' }, aliasId: { type: ['string', 'null'], format: 'uuid' },
status: { type: 'string' }, status: { type: 'string' },
global: { type: ['boolean', 'null'] },
}, },
} }
} }
......
...@@ -142,16 +142,16 @@ const resolvers = { ...@@ -142,16 +142,16 @@ const resolvers = {
return identities return identities
}, },
}, },
LocalIdentity: { // LocalIdentity: {
__isTypeOf: (obj, context, info) => obj.type === 'local', // __isTypeOf: (obj, context, info) => obj.type === 'local',
async email(obj, args, ctx, info) { // async email(obj, args, ctx, info) {
// Emails stored on user, but surfaced in local identity too // // Emails stored on user, but surfaced in local identity too
return (await ctx.loaders.User.load(obj.userId)).email // return (await ctx.loaders.User.load(obj.userId)).email
}, // },
}, // },
ExternalIdentity: { // ExternalIdentity: {
__isTypeOf: (obj, context, info) => obj.type !== 'local', // __isTypeOf: (obj, context, info) => obj.type !== 'local',
}, // },
} }
const typeDefs = ` const typeDefs = `
...@@ -209,33 +209,34 @@ const typeDefs = ` ...@@ -209,33 +209,34 @@ const typeDefs = `
roles: [String] roles: [String]
} }
interface Identity { type Identity {
id: ID id: ID
name: String name: String
aff: String # JATS <aff> aff: String # JATS <aff>
email: String # JATS <aff> email: String # JATS <aff>
type: String type: String
identifier: String
} }
# union Identity = Local | External # union Identity = Local | External
# local identity (not from ORCID, etc.) # local identity (not from ORCID, etc.)
type LocalIdentity implements Identity { #type LocalIdentity implements Identity {
id: ID # id: ID
name: String # name: String
email: String # email: String
aff: String # aff: String
type: String # type: String
} #}
#
type ExternalIdentity implements Identity { #type ExternalIdentity implements Identity {
id: ID # id: ID
name: String # name: String
identifier: String # identifier: String
email: String # email: String
aff: String # aff: String
type: String # type: String
} #}
input UserInput { input UserInput {
username: String! username: String!
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment