diff --git a/server/app.js b/server/app.js
index daf5ac7cb675e63ed57fc5062a4819dfb53ce93e..22ada082520d1dc728d3effe9d194c0bad60343f 100644
--- a/server/app.js
+++ b/server/app.js
@@ -60,8 +60,10 @@ const configureApp = app => {
 
   if (config.has('pubsweet-server.uploads')) {
     app.use(
-      '/uploads',
-      express.static(path.resolve(config.get('pubsweet-server.uploads'))),
+      '/static/uploads',
+      express.static(
+        path.join(__dirname, '..', config.get('pubsweet-server.uploads')),
+      ),
     )
   }
   // Passport strategies
diff --git a/server/model-file/src/resolvers.js b/server/model-file/src/resolvers.js
index 83b647dcdc7021b66cdcdcd56efb651e733894bd..da8ea91c7c9c0b3e9a1423ac232df849defd7a10 100644
--- a/server/model-file/src/resolvers.js
+++ b/server/model-file/src/resolvers.js
@@ -1,10 +1,37 @@
+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 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 = {
   Query: {},
   Mutation: {
-    async createFile(_, { id, file }, ctx) {
-      const data = await new File(file).save()
+    async createFile(_, { file, meta }, ctx) {
+      const path = await upload(file)
+      meta.url = `/static/${path}`
+      const data = await new File(meta).save()
       return data
     },
   },
diff --git a/server/model-file/src/typeDefs.js b/server/model-file/src/typeDefs.js
index 9b70a794c1eb119ef525b4dde7be3caa29dd0524..34050c96a91107418bba4e9e5951163a8bc5dc72 100644
--- a/server/model-file/src/typeDefs.js
+++ b/server/model-file/src/typeDefs.js
@@ -1,6 +1,17 @@
 const typeDefs = `
   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  {
diff --git a/server/model-manuscript/src/graphql.js b/server/model-manuscript/src/graphql.js
index e615f43bea6c281801fcd8f1e8f7ae9cd7eadf0a..25fdb53ae256593126a639974656061405cd3617 100644
--- a/server/model-manuscript/src/graphql.js
+++ b/server/model-manuscript/src/graphql.js
@@ -143,18 +143,74 @@ const resolvers = {
     },
     async updateManuscript(_, { id, input }, ctx) {
       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)
-      return ctx.models.Manuscript.update(id, update, ctx)
+      return ctx.models.Manuscript.query().updateAndFetchById(id, update)
     },
     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.status = decision
 
       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: {
     async manuscript(_, { id }, ctx) {
@@ -277,6 +333,8 @@ const typeDefs = `
     deleteManuscript(id: ID!): ID!
     reviewerResponse(currentUserId: ID, action: String, teamId: ID! ): Team
     assignTeamEditor(id: ID!, input: String): [Team]
+    addReviewer(manuscriptId: ID!, userId: ID!): Team
+    removeReviewer(manuscriptId: ID!, userId: ID!): Team
   }
 
   type Manuscript implements Object {
diff --git a/server/model-review/src/resolvers.js b/server/model-review/src/resolvers.js
index 90b611e5b4d76d6fc766d058016f5618cd776adb..9a8fc508e4d9f8d9e78e90e6e26bc7e8a9d8882a 100644
--- a/server/model-review/src/resolvers.js
+++ b/server/model-review/src/resolvers.js
@@ -5,11 +5,13 @@ const resolvers = {
   Mutation: {
     async updateReview(_, { id, input }, ctx) {
       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)
-        await ctx.connectors.Review.update(id, update, ctx)
         // Load Review
-        const rvw = await new Review(update)
+        const rvw = await ctx.models.Review.query().updateAndFetchById(
+          id,
+          update,
+        )
         rvw.comments = await rvw.getComments()
 
         return rvw
@@ -21,6 +23,25 @@ const resolvers = {
 
       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()
+    },
   },
 }
 
diff --git a/server/model-review/src/typeDefs.js b/server/model-review/src/typeDefs.js
index 0ded633743461e8c3a4fb01fc3d58ca4b5815f43..fb39b5e3903bb16633a2860330a201fa9a683165 100644
--- a/server/model-review/src/typeDefs.js
+++ b/server/model-review/src/typeDefs.js
@@ -1,9 +1,10 @@
 const typeDefs = `
   extend type Mutation {
     updateReview(id: ID, input: ReviewInput): Review!
+    completeReview(id: ID!): TeamMember
   }
 
-  type Review implements Object  {
+  type Review implements Object {
     id: ID!
     created: DateTime!
     updated: DateTime
diff --git a/server/model-team/src/graphql.js b/server/model-team/src/graphql.js
index 0f1b08e636c84e1152f854635976f1877c67c873..ed95c10c5e82b7c21915ba55dc57bd020e5d6c30 100644
--- a/server/model-team/src/graphql.js
+++ b/server/model-team/src/graphql.js
@@ -98,7 +98,7 @@ const typeDefs = `
     id: ID!
     type: String!
     role: String!
-    name: String!
+    name: String
     object: TeamObject
     members: [TeamMember!]
     owners: [User]
diff --git a/server/model-team/src/team_member.js b/server/model-team/src/team_member.js
index 51236eff1e88fb38c0b01d77d90d400a0e7a0812..eff8dd732633a4249c9913b8f2ac221dec14dded 100644
--- a/server/model-team/src/team_member.js
+++ b/server/model-team/src/team_member.js
@@ -43,7 +43,6 @@ class TeamMember extends BaseModel {
         teamId: { type: 'string', format: 'uuid' },
         aliasId: { type: ['string', 'null'], format: 'uuid' },
         status: { type: 'string' },
-        global: { type: ['boolean', 'null'] },
       },
     }
   }
diff --git a/server/model-user/src/graphql.js b/server/model-user/src/graphql.js
index 56565dd4a4645070bf9d86913cd82d012e87b102..241ca795f8d0622f0da7d0ff144ca53a494ea721 100644
--- a/server/model-user/src/graphql.js
+++ b/server/model-user/src/graphql.js
@@ -142,16 +142,16 @@ const resolvers = {
       return identities
     },
   },
-  LocalIdentity: {
-    __isTypeOf: (obj, context, info) => obj.type === 'local',
-    async email(obj, args, ctx, info) {
-      // Emails stored on user, but surfaced in local identity too
-      return (await ctx.loaders.User.load(obj.userId)).email
-    },
-  },
-  ExternalIdentity: {
-    __isTypeOf: (obj, context, info) => obj.type !== 'local',
-  },
+  // LocalIdentity: {
+  //   __isTypeOf: (obj, context, info) => obj.type === 'local',
+  //   async email(obj, args, ctx, info) {
+  //     // Emails stored on user, but surfaced in local identity too
+  //     return (await ctx.loaders.User.load(obj.userId)).email
+  //   },
+  // },
+  // ExternalIdentity: {
+  //   __isTypeOf: (obj, context, info) => obj.type !== 'local',
+  // },
 }
 
 const typeDefs = `
@@ -209,33 +209,34 @@ const typeDefs = `
     roles: [String]
   }
 
-  interface Identity {
+  type Identity {
     id: ID
     name: String
     aff: String # JATS <aff>
     email: String # JATS <aff>
     type: String
+    identifier: String
   }
 
   # union Identity = Local | External
 
   # local identity (not from ORCID, etc.)
-  type LocalIdentity implements Identity {
-    id: ID
-    name: String
-    email: String
-    aff: String
-    type: String
-  }
-
-  type ExternalIdentity implements Identity {
-    id: ID
-    name: String
-    identifier: String
-    email: String
-    aff: String
-    type: String
-  }
+  #type LocalIdentity implements Identity {
+  #  id: ID
+  #  name: String
+  #  email: String
+  #  aff: String
+  #  type: String
+  #}
+  #
+  #type ExternalIdentity implements Identity {
+  #  id: ID
+  #  name: String
+  #  identifier: String
+  #  email: String
+  #  aff: String
+  #  type: String
+  #}
 
   input UserInput {
     username: String!