Skip to content

Commit

Permalink
feat: work on department level pages in portals
Browse files Browse the repository at this point in the history
albanm committed May 3, 2024
1 parent 01b78ba commit 3101e53
Showing 7 changed files with 247 additions and 128 deletions.
4 changes: 4 additions & 0 deletions contract/page.json
Original file line number Diff line number Diff line change
@@ -15,6 +15,10 @@
},
"readOnly": true
},
"department": {
"type": "string",
"readOnly": true
},
"title": {
"type": "string",
"title": "Titre de la page",
28 changes: 26 additions & 2 deletions public/components/create-page-menu.vue
Original file line number Diff line number Diff line change
@@ -35,6 +35,16 @@
ref="form"
v-model="valid"
>
<v-select
v-if="departments && departments.length > 1"
v-model="editItem.department"
:items="departments"
dense
clearable
item-text="name"
item-value="id"
label="Département gestionnaire de la page"
/>
<slot
name="form"
:valid="valid"
@@ -69,14 +79,20 @@
</template>

<script>
const { mapGetters } = require('vuex')
const schema = require('../../contract/page.json')
export default {
data: () => ({
menu: false,
valid: false,
editItem: {}
editItem: { department: null },
owners: null
}),
computed: {
...mapGetters(['directoryUrl']),
...mapGetters('session', ['activeAccount']),
schema () {
const s = JSON.parse(JSON.stringify(schema))
delete s.properties.publishedAt
@@ -91,13 +107,21 @@ export default {
watch: {
menu () {
if (!this.menu) {
this.editItem = {}
this.editItem = { department: null }
}
}
},
async mounted () {
if (this.activeAccount.type === 'organization' && !this.activeAccount.department) {
const org = await this.$axios.$get(`${this.directoryUrl}/api/organizations/${this.activeAccount.id}`)
this.departments = org.departments || []
}
},
methods: {
confirm () {
if (this.$refs.form.validate()) {
const page = { ...this.editItem }
if (!page.department) delete page.department
this.$emit('created', this.editItem)
this.menu = false
}
25 changes: 16 additions & 9 deletions public/pages/manager/portals/_portalId/pages/_id/edit.vue
Original file line number Diff line number Diff line change
@@ -65,24 +65,29 @@ import Blank from '~/components/pages/blank.vue'
import Thematic from '~/components/pages/thematic.vue'
import News from '~/components/pages/news.vue'
import Event from '~/components/pages/event.vue'
const { mapState } = require('vuex')
const { mapState, mapGetters } = require('vuex')
const pageSchema = require('~/../contract/page.json')
Object.keys(pageSchema.properties).forEach(p => {
if (pageSchema.properties[p].readOnly) delete pageSchema.properties[p]
})
const adminOnlyParts = ['published', 'publishedAt', 'public', 'navigation'] // copied in portals.js
export default {
components: { Blank, Thematic, News, Event },
data: () => ({
page: null,
pageConfig: null,
htmlPage: null,
owner: null,
pageSchema
owner: null
}),
computed: {
...mapState(['config', 'portal']),
...mapGetters(['canAdmin']),
pageSchema () {
Object.keys(pageSchema.properties).forEach(p => {
if (pageSchema.properties[p].readOnly) delete pageSchema.properties[p]
else if (!this.canAdmin && adminOnlyParts.includes(p)) pageSchema.properties[p]['x-options'] = { disableAll: true }
})
return pageSchema
},
dataFairUrl () {
return this.$store.getters.dataFairUrl
},
@@ -96,7 +101,8 @@ export default {
page: {
id: this.$route.params.id,
title: this.page.title
}
},
canAdmin: this.canAdmin
},
arrayItemCardProps: { outlined: true, tile: true },
hideReadOnlyEmpty: true,
@@ -119,7 +125,7 @@ export default {
to: '/manager/portals'
}, {
text: this.portal.title,
to: `/manager/portals/${this.portal._id}`
to: this.canAdmin ? `/manager/portals/${this.portal._id}` : null
}, {
text: 'pages',
to: `/manager/portals/${this.portal._id}/pages`
@@ -137,6 +143,7 @@ export default {
try {
this.page = await this.$axios.$patch(this.$store.state.publicUrl + `/api/v1/portals/${this.portal._id}/pages/${this.$route.params.id}`, patch)
delete this.page.config
delete this.page.configDraft
delete this.page.id
delete this.page.portal
delete this.page.created
9 changes: 6 additions & 3 deletions public/pages/manager/portals/_portalId/pages/index.vue
Original file line number Diff line number Diff line change
@@ -77,6 +77,7 @@
>
{{ page.public ? 'mdi-lock-open' : 'mdi-lock' }}
</v-icon>
<span class="text-caption">{{ page.department }}</span>
<v-spacer />
<v-btn
icon
@@ -158,7 +159,7 @@
<script>
import CreatePageMenu from '~/components/create-page-menu.vue'
import RemoveConfirm from '~/components/remove-confirm.vue'
import { mapState } from 'vuex'
import { mapState, mapGetters } from 'vuex'
const pageSchema = require('~/../contract/page.json')
export default {
@@ -174,18 +175,20 @@ export default {
}),
computed: {
...mapState(['portal']),
...mapGetters(['canAdmin']),
templateNames () {
return this.pageSchema.properties.template.oneOf.reduce((a, item) => { a[item.const] = item.title; return a }, {})
}
},
mounted: async function () {
console.log('mounted', this.portal, this.canAdmin)
this.$store.dispatch('setManagerBreadcrumbs', [{
text: 'portails',
to: '/manager/portals',
disabled: false
}, {
text: this.portal.title,
to: `/manager/portals/${this.portal._id}`
to: this.canAdmin ? `/manager/portals/${this.portal._id}` : null
}, {
text: 'pages'
}])
@@ -196,7 +199,7 @@ export default {
this.loading = true
if (append) this.pagination += 1
else this.pagination = 1
const params = { size: 12, page: this.pagination, sort: 'created.date:-1' }
const params = { size: 12, page: this.pagination, sort: 'created.date:-1', edit: true }
if (this.filters.template) params.template = this.filters.template
const pages = await this.$axios.$get(this.$store.state.publicUrl + `/api/v1/portals/${this.portal._id}/pages`, { params })
if (append) pages.results.forEach(r => this.pages.results.push(r))
206 changes: 124 additions & 82 deletions public/pages/manager/portals/index.vue
Original file line number Diff line number Diff line change
@@ -12,86 +12,92 @@
Dans le second cas vous devez être propriétaire du nom de domaine en question et en capacité de définir ses règles DNS.
</p>
</v-row>
<v-row class="mt-6">
<v-card
v-if="portals && portals.length"
min-width="500"
tile
outlined
<template v-if="portals && portals.length">
<v-row
v-for="owner in portalsByOwner.owners"
:key="owner.key"
class="mt-6"
>
<v-list class="py-0">
<template v-for="portal in portals">
<v-list-item :key="portal._id">
<v-list-item-content>
<v-list-item-title>
<a
:href="portal.link"
target="_blank"
>{{ portal.title }}</a>
</v-list-item-title>
<v-list-item-subtitle>
<span>{{ portal.owner.name }}</span>
<span v-if="portal.owner.department"> / {{ portal.owner.departmentName || portal.owner.department }}</span>
</v-list-item-subtitle>
</v-list-item-content>
<v-col>
<h2 class="text-h5 mb-2">
{{ owner.fullName }}
</h2>
<v-card
width="500"
tile
outlined
>
<v-list class="py-0">
<template v-for="portal in portalsByOwner.portals[owner.key]">
<v-list-item :key="portal._id">
<v-list-item-content>
<v-list-item-title>
<a
:href="portal.link"
target="_blank"
>{{ portal.title }}</a>
</v-list-item-title>
</v-list-item-content>

<v-list-item-action>
<v-btn
:to="{name: 'manager-portals-portalId', params: {portalId: portal._id}}"
nuxt
icon
color="primary"
title="gérer le portail"
>
<v-icon>mdi-pencil</v-icon>
</v-btn>
</v-list-item-action>
<v-list-item-action>
<v-btn
:to="{name: 'manager-portals-portalId-pages', params: {portalId: portal._id}}"
nuxt
icon
color="primary"
title="éditer les pages de contenu"
>
<v-icon>mdi-text-box-edit</v-icon>
</v-btn>
</v-list-item-action>
<v-list-item-action>
<v-btn
:to="{name: 'manager-portals-portalId-uses', params: {portalId: portal._id}}"
nuxt
icon
color="primary"
title="gérer les réutilisations"
>
<v-icon
color="primary"
style="position:relative;top:-4px;"
>
mdi-share
</v-icon>
<v-icon
color="primary"
size="16"
style="position:absolute;bottom:-3px;right:-3px;"
>
mdi-pencil
</v-icon>
</v-btn>
</v-list-item-action>
<v-list-item-action>
<remove-confirm
:label="portal.title"
@removed="deletePortal(portal)"
/>
</v-list-item-action>
</v-list-item>
<v-divider :key="portal._id + '-divider'" />
</template>
</v-list>
</v-card>
</v-row>
<v-list-item-action v-if="owner.canAdmin">
<v-btn
:to="{name: 'manager-portals-portalId', params: {portalId: portal._id}}"
nuxt
icon
color="primary"
title="gérer le portail"
>
<v-icon>mdi-pencil</v-icon>
</v-btn>
</v-list-item-action>
<v-list-item-action>
<v-btn
:to="{name: 'manager-portals-portalId-pages', params: {portalId: portal._id}}"
nuxt
icon
color="primary"
title="éditer les pages de contenu"
>
<v-icon>mdi-text-box-edit</v-icon>
</v-btn>
</v-list-item-action>
<v-list-item-action v-if="owner.canAdmin">
<v-btn
:to="{name: 'manager-portals-portalId-uses', params: {portalId: portal._id}}"
nuxt
icon
color="primary"
title="gérer les réutilisations"
>
<v-icon
color="primary"
style="position:relative;top:-4px;"
>
mdi-share
</v-icon>
<v-icon
color="primary"
size="16"
style="position:absolute;bottom:-3px;right:-3px;"
>
mdi-pencil
</v-icon>
</v-btn>
</v-list-item-action>
<v-list-item-action v-if="owner.canAdmin">
<remove-confirm
:label="portal.title"
@removed="deletePortal(portal)"
/>
</v-list-item-action>
</v-list-item>
<v-divider :key="portal._id + '-divider'" />
</template>
</v-list>
</v-card>
</v-col>
</v-row>
</template>
</v-col>
</v-container>
</v-col>
@@ -136,7 +142,7 @@
@keyup.enter.native="createPortal(); showCreateMenu = false"
/>
<v-select
v-if="(!user.organization || !user.organization.department) && owners && owners.length > 1"
v-if="owners && owners.length > 1"
v-model="newPortal.owner"
:items="owners"
return-object
@@ -188,7 +194,43 @@ export default {
computed: {
...mapState('session', ['user', 'initialized']),
...mapGetters(['directoryUrl']),
...mapGetters('session', ['activeAccount'])
...mapGetters('session', ['activeAccount']),
portalsByOwner () {
if (!this.portals) return
const portalsByOwner = { portals: {}, owners: [] }
if (this.activeAccount.department) {
if (this.portals.some(p => p.owner.department === this.activeAccount.department)) {
portalsByOwner.owners.push({
...this.activeAccount,
key: `${this.activeAccount.type}/${this.activeAccount.id}/${this.activeAccount.department}`,
fullName: `${this.activeAccount.name} / ${this.activeAccount.departmentName || this.activeAccount.department}`,
canAdmin: true
})
}
} else {
if (this.portals.some(p => !p.department)) {
portalsByOwner.owners.push({
...this.activeAccount,
key: `${this.activeAccount.type}/${this.activeAccount.id}`,
fullName: this.activeAccount.name,
canAdmin: true
})
}
}
for (const portal of this.portals) {
const key = `${portal.owner.type}/${portal.owner.id}${portal.owner.department ? `/${portal.owner.department}` : ''}`
if (!portalsByOwner.portals[key]) portalsByOwner.portals[key] = []
portalsByOwner.portals[key].push(portal)
if (!portalsByOwner.owners.find(o => o.key === key)) {
portalsByOwner.owners.push({
...portal.owner,
key,
fullName: `${portal.owner.name}${portal.owner.department ? ` / ${portal.owner.departmentName || portal.owner.department}` : ''}`
})
}
}
return portalsByOwner
}
},
watch: {
showCreateMenu () {
@@ -199,8 +241,8 @@ export default {
},
async mounted () {
await this.refresh()
this.owners = [{ type: this.activeAccount.type, id: this.activeAccount.id, name: this.activeAccount.name, label: this.activeAccount.name }]
if (this.activeAccount.type === 'organization') {
if (this.activeAccount.type === 'organization' && !this.activeAccount.department) {
this.owners = [{ type: this.activeAccount.type, id: this.activeAccount.id, name: this.activeAccount.name, label: this.activeAccount.name }]
const org = await this.$axios.$get(`${this.directoryUrl}/api/organizations/${this.activeAccount.id}`)
for (const dep of (org.departments || [])) {
this.owners.push({
6 changes: 6 additions & 0 deletions public/store/index.js
Original file line number Diff line number Diff line change
@@ -52,6 +52,12 @@ export default () => {
if (state.config.department) owner += ':' + state.config.owner.department
return owner
},
canAdmin (state, getters) {
if (!state.config) return false
const activeAccount = getters['session/activeAccount']
if (!activeAccount) return false
return getters['session/isAccountAdmin'] && activeAccount.type === state.config.owner.type && activeAccount.id === state.config.owner.id && (!activeAccount.department || activeAccount.department === state.config.owner.department)
},
whiteLabel (state, getters) {
return state.whiteLabelOwners.includes(getters.owner)
},
97 changes: 65 additions & 32 deletions server/router/portals.js
Original file line number Diff line number Diff line change
@@ -178,15 +178,20 @@ const router = module.exports = express.Router()
// List user portals, or all portals for super admin
router.get('', asyncWrap(async (req, res) => {
if (!req.user) return res.status(401).send()
const filter = {}
let filter = {}
if (req.query.showAll && !req.user.adminMode) {
return res.status(403).send('Seul un super administrateur peut requêter la liste complète des portails.')
}
if (!req.query.showAll) {
filter['owner.type'] = req.user.activeAccount.type
filter['owner.id'] = req.user.activeAccount.id
if (req.user.activeAccount.department) {
filter['owner.department'] = req.user.activeAccount.department
filter = {
$or: [
{ ...filter, 'owner.department': req.user.activeAccount.department },
{ ...filter, 'owner.department': { $exists: false } }
]
}
}
}
res.send((await req.app.get('db').collection('portals').find(filter).limit(10000).toArray()).map(cleanPortal))
@@ -245,24 +250,24 @@ router.post('', asyncWrap(async (req, res) => {
}))

// Read the portal matching a request, check that the user is owner
async function setPortalAdmin (req, res, next) {
const setPortalAdmin = (ignoreDepartment = false) => async (req, res, next) => {
if (!req.user) return res.status(401).send()
const portal = await req.app.get('db')
.collection('portals').findOne({ _id: req.params.id })
if (!portal) throw createError(404, 'Portail inconnu')
if (portal.owner.type === 'organization') {
const orga = req.user.organizations.find(o => o.id === portal.owner.id && (!o.department || o.department === portal.owner.department))
if (!orga || orga.role !== 'admin') throw createError(403, 'admin only')
if (req.user.activeAccount.type !== portal.owner.type || req.user.activeAccount.id !== portal.owner.id) {
throw createError(403, 'owner only')
}
if (portal.owner.type === 'user' && req.user.id !== portal.owner.id) {
throw createError(403, 'user himself only')
if (!ignoreDepartment && req.user.activeAccount.department && req.user.activeAccount.department !== portal.owner.department) {
throw createError(403, 'owner department only')
}
if (req.user.activeAccount.role !== 'admin') throw createError(403, 'admin only')
req.portal = portal
if (next) next()
}

// Get an existing portal as the owner
router.get('/:id', asyncWrap(setPortalAdmin), asyncWrap(async (req, res) => {
router.get('/:id', asyncWrap(setPortalAdmin(true)), asyncWrap(async (req, res) => {
if (req.query.noConfig === 'true') {
delete req.portal.config
delete req.portal.configDraft
@@ -282,7 +287,7 @@ router.get('/:id', asyncWrap(setPortalAdmin), asyncWrap(async (req, res) => {
}))

// Delete an existing portal as the owner
router.delete('/:id', asyncWrap(setPortalAdmin), asyncWrap(async (req, res) => {
router.delete('/:id', asyncWrap(setPortalAdmin()), asyncWrap(async (req, res) => {
await req.app.get('db')
.collection('portals').deleteOne({ _id: req.params.id })
if (await fs.pathExists(`${config.dataDir}/${req.params.id}`)) await fs.remove(`${config.dataDir}/${req.params.id}`)
@@ -291,7 +296,7 @@ router.delete('/:id', asyncWrap(setPortalAdmin), asyncWrap(async (req, res) => {
}))

// Update the draft configuration as the owner
router.put('/:id/configDraft', asyncWrap(setPortalAdmin), asyncWrap(async (req, res) => {
router.put('/:id/configDraft', asyncWrap(setPortalAdmin()), asyncWrap(async (req, res) => {
req.body.updatedAt = new Date().toISOString()
await fillConfigAssets(`${config.dataDir}/${req.portal._id}/draft`, req.body)
await req.app.get('db')
@@ -303,7 +308,7 @@ const googleFonts = require('google-fonts-complete')
const fonts = Object.entries(googleFonts)
.filter(entry => entry[1].subsets.includes('latin'))
.map(([name, info]) => ({ source: 'google-fonts', name, label: name, category: info.category }))
router.get('/:id/fonts', asyncWrap(setPortalAdmin), (req, res, next) => {
router.get('/:id/fonts', asyncWrap(setPortalAdmin()), (req, res, next) => {
const assetsFonts = []
if (req.portal.configDraft?.assets?.font1?.name) {
assetsFonts.push({ source: 'assets', name: 'font1', label: `Police 1 (${req.portal.configDraft.assets.font1.name})` })
@@ -314,7 +319,7 @@ router.get('/:id/fonts', asyncWrap(setPortalAdmin), (req, res, next) => {
res.send([...assetsFonts, ...fonts])
})

router.post('/:id/assets/:assetId', asyncWrap(setPortalAdmin), asyncWrap(async (req, res) => {
router.post('/:id/assets/:assetId', asyncWrap(setPortalAdmin()), asyncWrap(async (req, res) => {
await uploadAsset(req, res)
await prepareFitHashedAsset(`${config.dataDir}/${req.params.id}/draft`, req.params.assetId)
res.send()
@@ -324,7 +329,7 @@ router.get('/:id/assets/:assetId', asyncWrap(setPortalAnonymous), asyncWrap(down

// Validate the draft as the owner
// Both configuration and uploaded resources
router.post('/:id/_validate_draft', asyncWrap(setPortalAdmin), asyncWrap(async (req, res) => {
router.post('/:id/_validate_draft', asyncWrap(setPortalAdmin()), asyncWrap(async (req, res) => {
await req.app.get('db')
.collection('portals').updateOne({ _id: req.portal._id }, {
$set: {
@@ -340,7 +345,7 @@ router.post('/:id/_validate_draft', asyncWrap(setPortalAdmin), asyncWrap(async (
await syncPortalUpdate({ ...req.portal, config: req.portal.configDraft }, req.headers.cookie)
res.send()
}))
router.post('/:id/_cancel_draft', asyncWrap(setPortalAdmin), asyncWrap(async (req, res) => {
router.post('/:id/_cancel_draft', asyncWrap(setPortalAdmin()), asyncWrap(async (req, res) => {
await req.app.get('db')
.collection('portals').updateOne({ _id: req.portal._id }, { $set: { configDraft: req.portal.config } })
if (await fs.pathExists(`${config.dataDir}/${req.portal._id}/prod`)) {
@@ -396,18 +401,26 @@ router.get('/:id/pages', asyncWrap(async (req, res, next) => {
const project = req.query.select ? Object.assign({}, ...req.query.select.split(',').map(f => ({ [f]: 1 }))) : {}
const sort = findUtils.sort(req.query.sort)
const pages = req.app.get('db').collection('pages')
const filter = { 'portal._id': req.params.id }
const filters = [{ 'portal._id': req.params.id }]
const [skip, size] = findUtils.pagination(req.query)
if (req.query.template) filter.template = req.query.template
if (!req.user) filter.public = true
else if (portal.owner.type === 'user' && portal.owner.id !== req.user.id) filter.public = true
else if (portal.owner.type === 'organization' && (!req.user.organization || portal.owner.id !== req.user.organization.id)) filter.public = true
else if (portal.owner.type === 'organization' && req.user.organization && req.user.organization.department && req.user.organization.department !== portal.owner.department) filter.public = true
if (filter.public || req.query.published === 'true') filter.published = true
if (req.query['future-events'] === 'true') filter['config.datetimes.end'] = { $gte: new Date().toISOString() }
if (req.query.template) filters.push({ template: req.query.template })
const publicFilter = { public: true, published: true }
if (!req.user) filters.push(publicFilter)
else if (portal.owner.type === 'user' && portal.owner.id !== req.user.id) filters.push(publicFilter)
else if (portal.owner.type === 'organization' && (!req.user.organization || portal.owner.id !== req.user.organization.id)) filters.push(publicFilter)
else if (portal.owner.type === 'organization' && req.user.organization && req.user.organization.department && req.user.organization.department !== portal.owner.department) {
filters.push({
$or: [
{ department: req.user.organization.department },
publicFilter
]
})
}
if (req.query.published === 'true') filters.push({ published: true })
if (req.query['future-events'] === 'true') filters.push({ 'config.datetimes.end': { $gte: new Date().toISOString() } })
const [results, count] = await Promise.all([
pages.find(filter).limit(size).skip(skip).project(project).sort(sort).toArray(),
pages.countDocuments(filter)
pages.find({ $and: filters }).limit(size).skip(skip).project(project).sort(sort).toArray(),
pages.countDocuments({ $and: filters })
])
results.forEach(page => cleanPage(page, req.query.html === 'true'))
res.json({ count, results })
@@ -425,7 +438,7 @@ router.get('/:id/pages/:pageId', asyncWrap(async (req, res, next) => {
if (portal.owner.type === 'organization' && (!req.user.organization || portal.owner.id !== req.user.organization.id)) {
return res.status(403).send()
}
if (portal.owner.type === 'organization' && req.user.organization && req.user.organization.department && req.user.organization.department !== portal.owner.department) {
if (portal.owner.type === 'organization' && req.user.organization && req.user.organization.department && req.user.organization.department !== portal.owner.department && req.user.organization.department !== page.department) {
return res.status(403).send()
}
}
@@ -434,7 +447,7 @@ router.get('/:id/pages/:pageId', asyncWrap(async (req, res, next) => {
}))

// Create a page
router.post('/:id/pages', asyncWrap(setPortalAdmin), asyncWrap(async (req, res, next) => {
router.post('/:id/pages', asyncWrap(setPortalAdmin(true)), asyncWrap(async (req, res, next) => {
const baseId = slug(req.body.title.slice(0, 100), { lower: true, strict: true })
req.body.id = baseId
req.body.portal = {
@@ -449,6 +462,9 @@ router.post('/:id/pages', asyncWrap(setPortalAdmin), asyncWrap(async (req, res,
if (req.body.published) {
req.body.publishedAt = new Date()
}
if (req.user.activeAccount.department && req.user.activeAccount.department !== req.portal.owner.department) {
req.body.department = req.user.activeAccount.department
}
const valid = validatePage(req.body)
if (!valid) return res.status(400).send(validatePage.errors)
let insertOk = false
@@ -468,17 +484,27 @@ router.post('/:id/pages', asyncWrap(setPortalAdmin), asyncWrap(async (req, res,
}))

// Patch some of the attributes of a page
router.patch('/:id/pages/:pageId', asyncWrap(setPortalAdmin), asyncWrap(async (req, res, next) => {
router.patch('/:id/pages/:pageId', asyncWrap(setPortalAdmin(true)), asyncWrap(async (req, res, next) => {
const filter = { id: req.params.pageId, 'portal._id': req.portal._id }
const page = await req.app.get('db').collection('pages')
.findOne(filter, { projection: { _id: 0 } })
if (!page) return res.status(404).send()
if (req.user.activeAccount.department && req.user.activeAccount.department !== req.portal.owner.department && req.user.activeAccount.department !== page.department) {
return res.status(403).send()
}
// Restrict the parts of the page that can be edited by API

const acceptedParts = Object.keys(pageSchema.properties).filter(k => !pageSchema.properties[k].readOnly)
acceptedParts.push('config')
const adminOnlyParts = ['published', 'publishedAt', 'public', 'navigation'] // copied in pages edit.vue

for (const key in req.body) {
if (!acceptedParts.includes(key)) return res.status(400).send('Unsupported patch part ' + key)
if (JSON.stringify(req.body[key]) === JSON.stringify(page[key] ?? false)) delete req.body[key]
else {
if (!acceptedParts.includes(key)) return res.status(400).send('Unsupported patch part ' + key)
if (page.department && page.department === req.user.activeAccount.department && adminOnlyParts.includes(key)) {
return res.status(400).send('Top organization admin only patch part ' + key)
}
}
}
req.body.updated = {
id: req.user.id,
@@ -509,8 +535,15 @@ router.patch('/:id/pages/:pageId', asyncWrap(setPortalAdmin), asyncWrap(async (r
res.status(200).send(patchedPage)
}))

router.delete('/:id/pages/:pageId', asyncWrap(setPortalAdmin), asyncWrap(async (req, res, next) => {
await req.app.get('db').collection('pages').deleteOne({ id: req.params.pageId, 'portal._id': req.portal._id })
router.delete('/:id/pages/:pageId', asyncWrap(setPortalAdmin(true)), asyncWrap(async (req, res, next) => {
const filter = { id: req.params.pageId, 'portal._id': req.portal._id }
const page = await req.app.get('db').collection('pages')
.findOne(filter, { projection: { _id: 0 } })
if (!page) return res.status(404).send()
if (req.user.activeAccount.department && req.user.activeAccount.department !== req.portal.owner.department && req.user.activeAccount.department !== page.department) {
return res.status(403).send()
}
await req.app.get('db').collection('pages').deleteOne(filter)
res.status(204).send()
}))

0 comments on commit 3101e53

Please sign in to comment.