mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Fix:User permissions for collection API routes and UI #951
This commit is contained in:
		
							parent
							
								
									e362456895
								
							
						
					
					
						commit
						8ec4bd4279
					
				@ -386,14 +386,14 @@ export default {
 | 
				
			|||||||
          {
 | 
					          {
 | 
				
			||||||
            func: 'toggleFinished',
 | 
					            func: 'toggleFinished',
 | 
				
			||||||
            text: `Mark as ${this.itemIsFinished ? 'Not Finished' : 'Finished'}`
 | 
					            text: `Mark as ${this.itemIsFinished ? 'Not Finished' : 'Finished'}`
 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
          {
 | 
					 | 
				
			||||||
            func: 'openCollections',
 | 
					 | 
				
			||||||
            text: 'Add to Collection'
 | 
					 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      if (this.userCanUpdate) {
 | 
					      if (this.userCanUpdate) {
 | 
				
			||||||
 | 
					        items.push({
 | 
				
			||||||
 | 
					          func: 'openCollections',
 | 
				
			||||||
 | 
					          text: 'Add to Collection'
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
        items.push({
 | 
					        items.push({
 | 
				
			||||||
          func: 'showEditModalFiles',
 | 
					          func: 'showEditModalFiles',
 | 
				
			||||||
          text: 'Files'
 | 
					          text: 'Files'
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,7 @@
 | 
				
			|||||||
    <div class="w-full h-full bg-primary relative rounded overflow-hidden">
 | 
					    <div class="w-full h-full bg-primary relative rounded overflow-hidden">
 | 
				
			||||||
      <covers-collection-cover ref="cover" :book-items="books" :width="width" :height="height" :book-cover-aspect-ratio="bookCoverAspectRatio" />
 | 
					      <covers-collection-cover ref="cover" :book-items="books" :width="width" :height="height" :book-cover-aspect-ratio="bookCoverAspectRatio" />
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <div v-show="isHovering" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-40 pointer-events-none">
 | 
					    <div v-show="isHovering && userCanUpdate" class="w-full h-full absolute top-0 left-0 z-10 bg-black bg-opacity-40 pointer-events-none">
 | 
				
			||||||
      <div class="absolute pointer-events-auto" :style="{ top: 0.5 * sizeMultiplier + 'rem', right: 0.5 * sizeMultiplier + 'rem' }" @click.stop.prevent="clickEdit">
 | 
					      <div class="absolute pointer-events-auto" :style="{ top: 0.5 * sizeMultiplier + 'rem', right: 0.5 * sizeMultiplier + 'rem' }" @click.stop.prevent="clickEdit">
 | 
				
			||||||
        <span class="material-icons text-xl text-white text-opacity-75 hover:text-opacity-100">edit</span>
 | 
					        <span class="material-icons text-xl text-white text-opacity-75 hover:text-opacity-100">edit</span>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
@ -69,6 +69,9 @@ export default {
 | 
				
			|||||||
    isAlternativeBookshelfView() {
 | 
					    isAlternativeBookshelfView() {
 | 
				
			||||||
      const constants = this.$constants || this.$nuxt.$constants
 | 
					      const constants = this.$constants || this.$nuxt.$constants
 | 
				
			||||||
      return this.bookshelfView == constants.BookshelfView.TITLES
 | 
					      return this.bookshelfView == constants.BookshelfView.TITLES
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    userCanUpdate() {
 | 
				
			||||||
 | 
					      return this.store.getters['user/getUserCanUpdate']
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  methods: {
 | 
					  methods: {
 | 
				
			||||||
 | 
				
			|||||||
@ -20,7 +20,7 @@
 | 
				
			|||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
          <div class="absolute bottom-0 left-0 right-0 w-full py-2 px-4 flex">
 | 
					          <div class="absolute bottom-0 left-0 right-0 w-full py-2 px-4 flex">
 | 
				
			||||||
            <ui-btn small color="error" type="button" @click.stop="removeClick">Remove</ui-btn>
 | 
					            <ui-btn v-if="userCanDelete" small color="error" type="button" @click.stop="removeClick">Remove</ui-btn>
 | 
				
			||||||
            <div class="flex-grow" />
 | 
					            <div class="flex-grow" />
 | 
				
			||||||
            <ui-btn color="success" type="submit">Save</ui-btn>
 | 
					            <ui-btn color="success" type="submit">Save</ui-btn>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
@ -85,6 +85,9 @@ export default {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    books() {
 | 
					    books() {
 | 
				
			||||||
      return this.collection.books || []
 | 
					      return this.collection.books || []
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    userCanDelete() {
 | 
				
			||||||
 | 
					      return this.$store.getters['user/getUserCanDelete']
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  methods: {
 | 
					  methods: {
 | 
				
			||||||
 | 
				
			|||||||
@ -27,15 +27,15 @@
 | 
				
			|||||||
        <span class="material-icons text-lg text-white text-opacity-70 hover:text-opacity-100 cursor-pointer">radio_button_unchecked</span>
 | 
					        <span class="material-icons text-lg text-white text-opacity-70 hover:text-opacity-100 cursor-pointer">radio_button_unchecked</span>
 | 
				
			||||||
      </div> -->
 | 
					      </div> -->
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
    <div class="w-40 absolute top-0 -right-24 h-full transform transition-transform" :class="!isHovering ? 'translate-x-0' : '-translate-x-24'">
 | 
					    <div class="w-40 absolute top-0 -right-24 h-full transform transition-transform" :class="!isHovering ? 'translate-x-0' : translateDistance">
 | 
				
			||||||
      <div class="flex h-full items-center">
 | 
					      <div class="flex h-full items-center">
 | 
				
			||||||
        <ui-tooltip :text="userIsFinished ? 'Mark as Not Finished' : 'Mark as Finished'" direction="top">
 | 
					        <ui-tooltip :text="userIsFinished ? 'Mark as Not Finished' : 'Mark as Finished'" direction="top">
 | 
				
			||||||
          <ui-read-icon-btn :disabled="isProcessingReadUpdate" :is-read="userIsFinished" borderless class="mx-1 mt-0.5" @click="toggleFinished" />
 | 
					          <ui-read-icon-btn :disabled="isProcessingReadUpdate" :is-read="userIsFinished" borderless class="mx-1 mt-0.5" @click="toggleFinished" />
 | 
				
			||||||
        </ui-tooltip>
 | 
					        </ui-tooltip>
 | 
				
			||||||
        <div class="mx-1" :class="isHovering ? '' : 'ml-6'">
 | 
					        <div v-if="userCanUpdate" class="mx-1" :class="isHovering ? '' : 'ml-6'">
 | 
				
			||||||
          <ui-icon-btn icon="edit" borderless @click="clickEdit" />
 | 
					          <ui-icon-btn icon="edit" borderless @click="clickEdit" />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="mx-1">
 | 
					        <div v-if="userCanDelete" class="mx-1">
 | 
				
			||||||
          <ui-icon-btn icon="close" borderless @click="removeClick" />
 | 
					          <ui-icon-btn icon="close" borderless @click="removeClick" />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
@ -71,6 +71,11 @@ export default {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  computed: {
 | 
					  computed: {
 | 
				
			||||||
 | 
					    translateDistance() {
 | 
				
			||||||
 | 
					      if (!this.userCanUpdate && !this.userCanDelete) return 'translate-x-0'
 | 
				
			||||||
 | 
					      else if (!this.userCanUpdate || !this.userCanDelete) return '-translate-x-12'
 | 
				
			||||||
 | 
					      return '-translate-x-24'
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    media() {
 | 
					    media() {
 | 
				
			||||||
      return this.book.media || {}
 | 
					      return this.book.media || {}
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@ -113,6 +118,12 @@ export default {
 | 
				
			|||||||
    coverWidth() {
 | 
					    coverWidth() {
 | 
				
			||||||
      if (this.bookCoverAspectRatio === 1) return this.coverSize * 1.6
 | 
					      if (this.bookCoverAspectRatio === 1) return this.coverSize * 1.6
 | 
				
			||||||
      return this.coverSize
 | 
					      return this.coverSize
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    userCanUpdate() {
 | 
				
			||||||
 | 
					      return this.$store.getters['user/getUserCanUpdate']
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    userCanDelete() {
 | 
				
			||||||
 | 
					      return this.$store.getters['user/getUserCanDelete']
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  methods: {
 | 
					  methods: {
 | 
				
			||||||
 | 
				
			|||||||
@ -19,9 +19,9 @@
 | 
				
			|||||||
              {{ streaming ? 'Streaming' : 'Play' }}
 | 
					              {{ streaming ? 'Streaming' : 'Play' }}
 | 
				
			||||||
            </ui-btn>
 | 
					            </ui-btn>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <ui-icon-btn icon="edit" class="mx-0.5" @click="editClick" />
 | 
					            <ui-icon-btn v-if="userCanUpdate" icon="edit" class="mx-0.5" @click="editClick" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <ui-icon-btn icon="delete" class="mx-0.5" @click="removeClick" />
 | 
					            <ui-icon-btn v-if="userCanDelete" icon="delete" class="mx-0.5" @click="removeClick" />
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <div class="my-8 max-w-2xl">
 | 
					          <div class="my-8 max-w-2xl">
 | 
				
			||||||
@ -92,6 +92,12 @@ export default {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    showPlayButton() {
 | 
					    showPlayButton() {
 | 
				
			||||||
      return this.playableBooks.length
 | 
					      return this.playableBooks.length
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    userCanUpdate() {
 | 
				
			||||||
 | 
					      return this.$store.getters['user/getUserCanUpdate']
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    userCanDelete() {
 | 
				
			||||||
 | 
					      return this.$store.getters['user/getUserCanDelete']
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  methods: {
 | 
					  methods: {
 | 
				
			||||||
 | 
				
			|||||||
@ -150,7 +150,7 @@
 | 
				
			|||||||
              <ui-read-icon-btn :disabled="isProcessingReadUpdate" :is-read="userIsFinished" class="mx-0.5" @click="toggleFinished" />
 | 
					              <ui-read-icon-btn :disabled="isProcessingReadUpdate" :is-read="userIsFinished" class="mx-0.5" @click="toggleFinished" />
 | 
				
			||||||
            </ui-tooltip>
 | 
					            </ui-tooltip>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            <ui-tooltip v-if="!isPodcast" text="Collections" direction="top">
 | 
					            <ui-tooltip v-if="!isPodcast && userCanUpdate" text="Collections" direction="top">
 | 
				
			||||||
              <ui-icon-btn icon="collections_bookmark" class="mx-0.5" outlined @click="collectionsClick" />
 | 
					              <ui-icon-btn icon="collections_bookmark" class="mx-0.5" outlined @click="collectionsClick" />
 | 
				
			||||||
            </ui-tooltip>
 | 
					            </ui-tooltip>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -24,18 +24,11 @@ class CollectionController {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  findOne(req, res) {
 | 
					  findOne(req, res) {
 | 
				
			||||||
    var collection = this.db.collections.find(c => c.id === req.params.id)
 | 
					    res.json(req.collection.toJSONExpanded(this.db.libraryItems))
 | 
				
			||||||
    if (!collection) {
 | 
					 | 
				
			||||||
      return res.status(404).send('Collection not found')
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    res.json(collection.toJSONExpanded(this.db.libraryItems))
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async update(req, res) {
 | 
					  async update(req, res) {
 | 
				
			||||||
    var collection = this.db.collections.find(c => c.id === req.params.id)
 | 
					    const collection = req.collection
 | 
				
			||||||
    if (!collection) {
 | 
					 | 
				
			||||||
      return res.status(404).send('Collection not found')
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    var wasUpdated = collection.update(req.body)
 | 
					    var wasUpdated = collection.update(req.body)
 | 
				
			||||||
    var jsonExpanded = collection.toJSONExpanded(this.db.libraryItems)
 | 
					    var jsonExpanded = collection.toJSONExpanded(this.db.libraryItems)
 | 
				
			||||||
    if (wasUpdated) {
 | 
					    if (wasUpdated) {
 | 
				
			||||||
@ -46,10 +39,7 @@ class CollectionController {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async delete(req, res) {
 | 
					  async delete(req, res) {
 | 
				
			||||||
    var collection = this.db.collections.find(c => c.id === req.params.id)
 | 
					    const collection = req.collection
 | 
				
			||||||
    if (!collection) {
 | 
					 | 
				
			||||||
      return res.status(404).send('Collection not found')
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    var jsonExpanded = collection.toJSONExpanded(this.db.libraryItems)
 | 
					    var jsonExpanded = collection.toJSONExpanded(this.db.libraryItems)
 | 
				
			||||||
    await this.db.removeEntity('collection', collection.id)
 | 
					    await this.db.removeEntity('collection', collection.id)
 | 
				
			||||||
    this.emitter('collection_removed', jsonExpanded)
 | 
					    this.emitter('collection_removed', jsonExpanded)
 | 
				
			||||||
@ -57,10 +47,7 @@ class CollectionController {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async addBook(req, res) {
 | 
					  async addBook(req, res) {
 | 
				
			||||||
    var collection = this.db.collections.find(c => c.id === req.params.id)
 | 
					    const collection = req.collection
 | 
				
			||||||
    if (!collection) {
 | 
					 | 
				
			||||||
      return res.status(404).send('Collection not found')
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    var libraryItem = this.db.libraryItems.find(li => li.id === req.body.id)
 | 
					    var libraryItem = this.db.libraryItems.find(li => li.id === req.body.id)
 | 
				
			||||||
    if (!libraryItem) {
 | 
					    if (!libraryItem) {
 | 
				
			||||||
      return res.status(500).send('Book not found')
 | 
					      return res.status(500).send('Book not found')
 | 
				
			||||||
@ -80,11 +67,7 @@ class CollectionController {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  // DELETE: api/collections/:id/book/:bookId
 | 
					  // DELETE: api/collections/:id/book/:bookId
 | 
				
			||||||
  async removeBook(req, res) {
 | 
					  async removeBook(req, res) {
 | 
				
			||||||
    var collection = this.db.collections.find(c => c.id === req.params.id)
 | 
					    const collection = req.collection
 | 
				
			||||||
    if (!collection) {
 | 
					 | 
				
			||||||
      return res.status(404).send('Collection not found')
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (collection.books.includes(req.params.bookId)) {
 | 
					    if (collection.books.includes(req.params.bookId)) {
 | 
				
			||||||
      collection.removeBook(req.params.bookId)
 | 
					      collection.removeBook(req.params.bookId)
 | 
				
			||||||
      var jsonExpanded = collection.toJSONExpanded(this.db.libraryItems)
 | 
					      var jsonExpanded = collection.toJSONExpanded(this.db.libraryItems)
 | 
				
			||||||
@ -96,10 +79,7 @@ class CollectionController {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  // POST: api/collections/:id/batch/add
 | 
					  // POST: api/collections/:id/batch/add
 | 
				
			||||||
  async addBatch(req, res) {
 | 
					  async addBatch(req, res) {
 | 
				
			||||||
    var collection = this.db.collections.find(c => c.id === req.params.id)
 | 
					    const collection = req.collection
 | 
				
			||||||
    if (!collection) {
 | 
					 | 
				
			||||||
      return res.status(404).send('Collection not found')
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!req.body.books || !req.body.books.length) {
 | 
					    if (!req.body.books || !req.body.books.length) {
 | 
				
			||||||
      return res.status(500).send('Invalid request body')
 | 
					      return res.status(500).send('Invalid request body')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -120,10 +100,7 @@ class CollectionController {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  // POST: api/collections/:id/batch/remove
 | 
					  // POST: api/collections/:id/batch/remove
 | 
				
			||||||
  async removeBatch(req, res) {
 | 
					  async removeBatch(req, res) {
 | 
				
			||||||
    var collection = this.db.collections.find(c => c.id === req.params.id)
 | 
					    const collection = req.collection
 | 
				
			||||||
    if (!collection) {
 | 
					 | 
				
			||||||
      return res.status(404).send('Collection not found')
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!req.body.books || !req.body.books.length) {
 | 
					    if (!req.body.books || !req.body.books.length) {
 | 
				
			||||||
      return res.status(500).send('Invalid request body')
 | 
					      return res.status(500).send('Invalid request body')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -141,5 +118,25 @@ class CollectionController {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    res.json(collection.toJSONExpanded(this.db.libraryItems))
 | 
					    res.json(collection.toJSONExpanded(this.db.libraryItems))
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  middleware(req, res, next) {
 | 
				
			||||||
 | 
					    if (req.params.id) {
 | 
				
			||||||
 | 
					      var collection = this.db.collections.find(c => c.id === req.params.id)
 | 
				
			||||||
 | 
					      if (!collection) {
 | 
				
			||||||
 | 
					        return res.status(404).send('Collection not found')
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      req.collection = collection
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (req.method == 'DELETE' && !req.user.canDelete) {
 | 
				
			||||||
 | 
					      Logger.warn(`[CollectionController] User attempted to delete without permission`, req.user.username)
 | 
				
			||||||
 | 
					      return res.sendStatus(403)
 | 
				
			||||||
 | 
					    } else if ((req.method == 'PATCH' || req.method == 'POST') && !req.user.canUpdate) {
 | 
				
			||||||
 | 
					      Logger.warn('[CollectionController] User attempted to update without permission', req.user.username)
 | 
				
			||||||
 | 
					      return res.sendStatus(403)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    next()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
module.exports = new CollectionController()
 | 
					module.exports = new CollectionController()
 | 
				
			||||||
@ -116,16 +116,16 @@ class ApiRouter {
 | 
				
			|||||||
    //
 | 
					    //
 | 
				
			||||||
    // Collection Routes
 | 
					    // Collection Routes
 | 
				
			||||||
    //
 | 
					    //
 | 
				
			||||||
    this.router.post('/collections', CollectionController.create.bind(this))
 | 
					    this.router.post('/collections', CollectionController.middleware.bind(this), CollectionController.create.bind(this))
 | 
				
			||||||
    this.router.get('/collections', CollectionController.findAll.bind(this))
 | 
					    this.router.get('/collections', CollectionController.findAll.bind(this))
 | 
				
			||||||
    this.router.get('/collections/:id', CollectionController.findOne.bind(this))
 | 
					    this.router.get('/collections/:id', CollectionController.middleware.bind(this), CollectionController.findOne.bind(this))
 | 
				
			||||||
    this.router.patch('/collections/:id', CollectionController.update.bind(this))
 | 
					    this.router.patch('/collections/:id', CollectionController.middleware.bind(this), CollectionController.update.bind(this))
 | 
				
			||||||
    this.router.delete('/collections/:id', CollectionController.delete.bind(this))
 | 
					    this.router.delete('/collections/:id', CollectionController.middleware.bind(this), CollectionController.delete.bind(this))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.router.post('/collections/:id/book', CollectionController.addBook.bind(this))
 | 
					    this.router.post('/collections/:id/book', CollectionController.middleware.bind(this), CollectionController.addBook.bind(this))
 | 
				
			||||||
    this.router.delete('/collections/:id/book/:bookId', CollectionController.removeBook.bind(this))
 | 
					    this.router.delete('/collections/:id/book/:bookId', CollectionController.middleware.bind(this), CollectionController.removeBook.bind(this))
 | 
				
			||||||
    this.router.post('/collections/:id/batch/add', CollectionController.addBatch.bind(this))
 | 
					    this.router.post('/collections/:id/batch/add', CollectionController.middleware.bind(this), CollectionController.addBatch.bind(this))
 | 
				
			||||||
    this.router.post('/collections/:id/batch/remove', CollectionController.removeBatch.bind(this))
 | 
					    this.router.post('/collections/:id/batch/remove', CollectionController.middleware.bind(this), CollectionController.removeBatch.bind(this))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //
 | 
					    //
 | 
				
			||||||
    // Current User Routes (Me)
 | 
					    // Current User Routes (Me)
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user