Skip to content

Commit 875b98d

Browse files
mepriprithehabes
andauthored
Project Update Roles (#291)
* Project Update Roles * Custom Role API * Update Change * changing variable name * upda;te comment * Use the static value from Group.defaultRoles * Revert "Merge branch 'development' into project-roles-fix" This reverts commit 4412996, reversing changes made to 2f3d09a. * Roles update --------- Co-authored-by: Bryan Haberberger <bryan.j.haberberger@slu.edu>
1 parent 85889cf commit 875b98d

10 files changed

Lines changed: 61 additions & 130 deletions

File tree

classes/Group/Group.js

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ export default class Group {
4040
return Object.fromEntries(roles.map(role => [role, allRoles[role]]))
4141
}
4242

43+
async getCustomRoles() {
44+
if (Object.keys(this.data.members).length === 0) {
45+
await this.#loadFromDB()
46+
}
47+
return this.data.customRoles
48+
}
49+
4350
getPermissions(role) {
4451
return Object.assign(Group.defaultRoles, this.data.customRoles)[role] ?? "x_x_x"
4552
}
@@ -180,14 +187,14 @@ export default class Group {
180187
return true
181188
}
182189

183-
async setCustomRoles(roles) {
190+
async updateCustomRoles(roles) {
184191
if (!Object.keys(this.data.members).length) {
185192
await this.#loadFromDB()
186193
}
187194
if (!this.isValidRolesMap(roles))
188195
throw new Error("Invalid roles. Must be a JSON Object with keys as roles and values as arrays of permissions or space-delimited strings.")
189196
this.data.customRoles = roles
190-
this.update()
197+
await this.update()
191198
}
192199

193200
async addCustomRoles(roleMap) {
@@ -197,34 +204,16 @@ export default class Group {
197204
if (!this.isValidRolesMap(roleMap))
198205
throw new Error("Invalid roles. Must be a JSON Object with keys as roles and values as arrays of permissions or space-delimited strings.")
199206
this.data.customRoles = { ...this.data.customRoles, ...roleMap }
200-
this.update()
207+
await this.update()
201208
}
202209

203-
async removeCustomRoles(roleMap) {
210+
async removeCustomRoles(roleName) {
204211
if (!Object.keys(this.data.members).length) {
205212
await this.#loadFromDB()
206213
}
207214

208-
if (!Array.isArray(roleMap)) {
209-
210-
if (this.isValidRolesMap(roleMap)) {
211-
for (const role in roleMap) {
212-
delete this.data.customRoles[role]
213-
}
214-
return this.update()
215-
}
216-
if (typeof roleMap !== "string") {
217-
throw {
218-
status: 400,
219-
message: "Invalid roles. Must be an array of strings or a JSON Object with keys as roles and values as arrays of permissions or space-delimited strings."
220-
}
221-
}
222-
roleMap = roleMap.toUpperCase().split(" ")
223-
224-
}
225-
226-
roleMap.map(role => delete this.data.customRoles[role])
227-
return this.update()
215+
delete this.data.customRoles[roleName]
216+
await this.update()
228217
}
229218

230219
async save() {

classes/Line/Line.js

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,6 @@ export default class Line {
124124
const currentValue = textualBody.value ?? textualBody.chars ?? textualBody['cnt:asChars'] ?? textualBody
125125
if (currentValue === text) return this
126126
Object.assign(textualBody, { type: 'TextualBody', value: text, format: options.format ?? "text/plain", language: options.language })
127-
// Apply options directly to the Annotation
128-
if (options.creator) this.creator = options.creator
129-
if (options.generator) this.generator = options.generator
130127
// discard Annotation-level options if only one body entry is modified.
131128
return this.update()
132129
}
@@ -145,7 +142,7 @@ export default class Line {
145142
throw new Error('Unexpected body format. Cannot update text.')
146143
}
147144

148-
async updateBounds({x, y, w, h}, options = {}) {
145+
async updateBounds({x, y, w, h}) {
149146
if (!x || !y || !w || !h) {
150147
throw new Error('Bounds ({x,y,w,h}) must be provided')
151148
}
@@ -155,10 +152,6 @@ export default class Line {
155152
return this
156153
}
157154
this.target = newTarget
158-
// Apply options directly to the Annotation.
159-
if (options.creator) this.creator = options.creator
160-
if (options.generator) this.generator = options.generator
161-
// discarding unknown options
162155
return this.update()
163156
}
164157

classes/Page/Page.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import dbDriver from "../../database/driver.js"
2-
import { handleVersionConflict, fetchUserAgent, upgradeReferences } from "../../utilities/shared.js"
2+
import { handleVersionConflict, fetchUserAgent } from "../../utilities/shared.js"
33
import ProjectFactory from "../Project/ProjectFactory.js"
44

55
const databaseTiny = new dbDriver("tiny")
@@ -30,7 +30,6 @@ export default class Page {
3030
Object.assign(this, { id, label, target, partOf: partOf ?? layerId, items, creator, prev, next })
3131
if (this.id.startsWith(process.env.RERUMIDPREFIX)) {
3232
this.#tinyAction = 'update'
33-
upgradeReferences(this, ['partOf'])
3433
}
3534
return this
3635
}

line/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ router.patch('/:lineId/text', auth0Middleware(), async (req, res) => {
160160
return
161161
}
162162
const line = new Line(oldLine)
163-
const updatedLine = await line.updateText(req.body, { creator: user._id })
163+
const updatedLine = await line.updateText(req.body)
164164
const lineIndex = page.items.findIndex(l => l.id.split('/').pop() === req.params.lineId?.split('/').pop())
165165
page.items[lineIndex] = updatedLine
166166
await withOptimisticLocking(
@@ -211,7 +211,7 @@ router.patch('/:lineId/bounds', auth0Middleware(), async (req, res) => {
211211
return
212212
}
213213
const line = new Line(oldLine)
214-
const updatedLine = await line.updateBounds(req.body, { creator: user._id })
214+
const updatedLine = await line.updateBounds(req.body)
215215
const lineIndex = page.items.findIndex(l => l.id.split('/').pop() === req.params.lineId?.split('/').pop())
216216
page.items[lineIndex] = updatedLine
217217
await withOptimisticLocking(

project/customRolesRouter.js

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,31 @@ import { ACTIONS, ENTITIES, SCOPES } from "./groups/permissions_parameters.js"
88

99
const router = express.Router({ mergeParams: true })
1010

11+
// Get custom roles from a Project's Group
12+
router.get('/:projectId/customRoles', auth0Middleware(), async (req, res) => {
13+
const { projectId } = req.params
14+
const user = req.user
15+
if (!user) return respondWithError(res, 401, "Unauthenticated request")
16+
try {
17+
const project = new Project(projectId)
18+
if (!project) {
19+
return respondWithError(res, 404, "Project not found")
20+
}
21+
if (!(await project.checkUserAccess(user._id, ACTIONS.READ, SCOPES.ALL, ENTITIES.GROUP))) {
22+
return respondWithError(res, 403, "You do not have permission to access this group.")
23+
}
24+
if (!project.data.group) {
25+
return respondWithError(res, 404, "Group not found for this project")
26+
}
27+
const group = new Group(project.data.group)
28+
const customRoles = await group.getCustomRoles()
29+
res.status(200).json(customRoles)
30+
} catch (error) {
31+
console.error(error)
32+
respondWithError(res, error.status ?? 500, error.message ?? "Internal Server Error")
33+
}
34+
})
35+
1136
// Add custom roles
1237
router.post('/:projectId/addCustomRoles', auth0Middleware(), async (req, res) => {
1338
const { projectId } = req.params
@@ -18,8 +43,7 @@ router.post('/:projectId/addCustomRoles', auth0Middleware(), async (req, res) =>
1843
return respondWithError(res, 400, "Custom roles must be provided as a JSON Object with keys as roles and values as arrays of permissions or space-delimited strings.")
1944
}
2045
try {
21-
customRoles = scrubDefaultRoles(customRoles)
22-
if (!customRoles) return respondWithError(res, 400, `No custom roles provided.`)
46+
if (!scrubDefaultRoles(customRoles)) return respondWithError(res, 400, `No custom roles provided.`)
2347
const project = new Project(projectId)
2448
if (!(await project.checkUserAccess(user._id, ACTIONS.CREATE, SCOPES.ALL, ENTITIES.ROLE))) {
2549
return respondWithError(res, 403, "You do not have permission to add custom roles.")
@@ -32,32 +56,31 @@ router.post('/:projectId/addCustomRoles', auth0Middleware(), async (req, res) =>
3256
}
3357
})
3458

35-
// Set custom roles
36-
router.put('/:projectId/setCustomRoles', auth0Middleware(), async (req, res) => {
59+
// Update custom roles
60+
router.put('/:projectId/updateCustomRoles', auth0Middleware(), async (req, res) => {
3761
const { projectId } = req.params
38-
let newCustomRoles = req.body.roles ?? req.body
62+
let roles = req.body.roles ?? req.body
3963
const user = req.user
4064
if (!user) return respondWithError(res, 401, "Unauthenticated request")
41-
if (!Object.keys(newCustomRoles).length) {
65+
if (!Object.keys(roles).length) {
4266
return respondWithError(res, 400, "Custom roles must be provided as a JSON Object with keys as roles and values as arrays of permissions or space-delimited strings.")
4367
}
4468
try {
45-
newCustomRoles = scrubDefaultRoles(newCustomRoles)
46-
if (!newCustomRoles) return respondWithError(res, 400, `No custom roles provided.`)
69+
if (!scrubDefaultRoles(roles)) return respondWithError(res, 400, `No custom roles provided.`)
4770
const project = new Project(projectId)
4871
if (!(await project.checkUserAccess(user._id, ACTIONS.UPDATE, SCOPES.ALL, ENTITIES.ROLE))) {
4972
return respondWithError(res, 403, "You do not have permission to set custom roles.")
5073
}
5174
const group = new Group(project.data.group)
52-
await group.setCustomRoles(newCustomRoles)
75+
await group.updateCustomRoles(roles)
5376
res.status(200).json({ message: 'Custom roles set successfully.' })
5477
} catch (error) {
5578
respondWithError(res, error.status ?? 500, error.message ?? 'Error setting custom roles.')
5679
}
5780
})
5881

5982
// Remove custom roles
60-
router.post('/:projectId/removeCustomRoles', auth0Middleware(), async (req, res) => {
83+
router.delete('/:projectId/removeCustomRoles', auth0Middleware(), async (req, res) => {
6184
const { projectId } = req.params
6285
let rolesToRemove = req.body.roles ?? req.body
6386
const user = req.user
@@ -72,16 +95,12 @@ router.post('/:projectId/removeCustomRoles', auth0Middleware(), async (req, res)
7295
return respondWithError(res, 400, "Roles to remove must be provided as an array of strings or a JSON Object with keys as roles and values as arrays of permissions or space-delimited strings.")
7396
}
7497
try {
75-
rolesToRemove = scrubDefaultRoles(rolesToRemove)
76-
if (!rolesToRemove) {
77-
return respondWithError(res, 400, `No custom roles provided.`)
78-
}
7998
const project = new Project(projectId)
8099
if (!(await project.checkUserAccess(user._id, ACTIONS.DELETE, SCOPES.ALL, ENTITIES.ROLE))) {
81100
return respondWithError(res, 403, "You do not have permission to remove custom roles.")
82101
}
83102
const group = new Group(project.data.group)
84-
await (await group.removeCustomRoles(rolesToRemove)).update()
103+
await group.removeCustomRoles(rolesToRemove)
85104
res.status(200).json({ message: 'Custom roles removed successfully.' })
86105
} catch (error) {
87106
respondWithError(res, error.status ?? 500, error.message ?? 'Error removing custom roles.')

project/index.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import metadataRouter from "./metadataRouter.js"
1212
import projectToolsRouter from "./projectToolsRouter.js"
1313
import memberUpgradeRouter from "./memberUpgradeRouter.js"
1414
import memberDeclineInviteRouter from "./memberDeclineInviteRouter.js"
15-
import memberLeaveRouter from "./memberLeaveRouter.js"
1615
import projectCopyRouter from "./projectCopyRouter.js"
1716

1817
const router = express.Router({ mergeParams: true })
@@ -21,7 +20,6 @@ router.use(cors(common_cors))
2120
// Use split routers
2221
router.use(memberUpgradeRouter) // Contains unauthenticated route!
2322
router.use(memberDeclineInviteRouter) // Contains unauthenticated route!
24-
router.use(memberLeaveRouter)
2523
router.use(projectCreateRouter)
2624
router.use(import28Router)
2725
router.use(projectReadRouter)

project/memberLeaveRouter.js

Lines changed: 0 additions & 52 deletions
This file was deleted.

utilities/isDefaultRole.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
import Group from "../classes/Group/Group.js"
1+
const internalRoles = {
2+
OWNER: ["*_*_*"],
3+
LEADER: ["UPDATE_*_PROJECT", "READ_*_PROJECT", "*_*_MEMBER", "*_*_ROLE", "*_*_PERMISSION", "*_*_LAYER", "*_*_PAGE"],
4+
CONTRIBUTOR: ["READ_*_*", "UPDATE_TEXT_*", "UPDATE_ORDER_*", "UPDATE_SELECTOR_*", "CREATE_SELECTOR_*", "DELETE_*_LINE", "UPDATE_DESCRIPTION_LAYER", "CREATE_*_LAYER"],
5+
VIEWER: ["READ_*_PROJECT", "READ_*_MEMBER", "READ_*_LAYER", "READ_*_PAGE", "READ_*_LINE"]
6+
}
27

38
export default function scrubDefaultRoles(roleName) {
4-
const defaultRoles = Object.keys(Group.defaultRoles)
9+
const defaultRoles = Object.keys(internalRoles)
510
if (Array.isArray(roleName)) {
611
roleName = roleName.filter(roleString => {
712
if (typeof roleString !== "string") throw new Error("Expecting a RolesMap and not an Array.")

utilities/shared.js

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -368,23 +368,3 @@ export const fetchUserAgent = async (userId) => {
368368
throw new Error(`Error fetching user agent: ${error.message}`)
369369
}
370370
}
371-
372-
/**
373-
* Upgrade references in an object to use the RERUMIDPREFIX for specified keys.
374-
* For each key, if the value is a string containing a "/", the prefix is replaced with process.env.RERUMIDPREFIX and the last segment.
375-
* For the key 'pages', if it is an array, each page object with an 'id' will have its id upgraded similarly.
376-
*
377-
* @param {object} obj - The object whose references should be upgraded.
378-
* @param {string[]} [keys=["partOf", "next", "prev", "target", "pages"]] - The keys to upgrade. Must be valid JS object keys.
379-
* @returns {object} The upgraded object.
380-
*/
381-
export function upgradeReferences(obj, keys = []) {
382-
if (!obj || typeof obj !== 'object') return obj
383-
keys.filter(key => typeof key === 'string' && /^[a-zA-Z_$][\w$]*$/.test(key)).forEach(key => {
384-
if (!obj[key]) return
385-
const hexString = obj[key].split('/').pop()
386-
if (!hexString) return
387-
obj[key] = `${process.env.RERUMIDPREFIX}${hexString}`
388-
})
389-
return obj
390-
}

utilities/validateURL.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ async function validateURL(url) {
2929
}
3030
}
3131

32-
return {valid: true}
33-
} catch (_error) {
34-
return {valid: false, message: "URL is not reachable", status: 500}
32+
return { valid: true }
33+
} catch {
34+
return { valid: false, message: "URL is not reachable", status: 500 }
3535
}
3636
}
3737

0 commit comments

Comments
 (0)