mirror of
https://github.com/nowcommunity/nowdatabase.git
synced 2026-10-12 19:13:52 +03:00
Add protected person deletion
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it } from '@jest/globals'
|
||||
import { pool, nowDb } from '../../utils/db'
|
||||
import { login, logout, noPermError, resetDatabase, resetDatabaseTimeout, send } from '../utils'
|
||||
|
||||
const createPerson = async (initials: string, userId?: number) => {
|
||||
await nowDb.com_people.create({
|
||||
data: {
|
||||
initials,
|
||||
first_name: 'Delete',
|
||||
surname: 'Candidate',
|
||||
full_name: 'Delete Candidate',
|
||||
email: `${initials.toLowerCase()}@example.com`,
|
||||
organization: 'Test organization',
|
||||
country: 'Finland',
|
||||
user_id: userId,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
describe('Deleting person works', () => {
|
||||
beforeAll(async () => {
|
||||
await resetDatabase()
|
||||
}, resetDatabaseTimeout)
|
||||
|
||||
beforeEach(async () => {
|
||||
await login()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await pool.end()
|
||||
})
|
||||
|
||||
it('deletes a person without user relation or protected activity', async () => {
|
||||
await createPerson('DEL-NO')
|
||||
|
||||
const deleteResult = await send('person/DEL-NO', 'DELETE')
|
||||
expect(deleteResult.status).toEqual(200)
|
||||
|
||||
const deletedPerson = await nowDb.com_people.findUnique({ where: { initials: 'DEL-NO' } })
|
||||
expect(deletedPerson).toBeNull()
|
||||
})
|
||||
|
||||
it('deletes a person and their user when the user has no protected activity', async () => {
|
||||
const user = await nowDb.com_users.create({
|
||||
data: {
|
||||
user_name: 'delete-person-user',
|
||||
now_user_group: 'eu',
|
||||
},
|
||||
})
|
||||
await createPerson('DEL-USR', user.user_id)
|
||||
|
||||
const deleteResult = await send('person/DEL-USR', 'DELETE')
|
||||
expect(deleteResult.status).toEqual(200)
|
||||
|
||||
const deletedPerson = await nowDb.com_people.findUnique({ where: { initials: 'DEL-USR' } })
|
||||
const deletedUser = await nowDb.com_users.findUnique({ where: { user_id: user.user_id } })
|
||||
expect(deletedPerson).toBeNull()
|
||||
expect(deletedUser).toBeNull()
|
||||
})
|
||||
|
||||
it('does not delete a person with update history or project and coordinator links', async () => {
|
||||
const deleteResult = await send<{ blockers: string[]; message: string }>('person/AD', 'DELETE')
|
||||
|
||||
expect(deleteResult.status).toEqual(409)
|
||||
expect(deleteResult.body.message).toEqual(
|
||||
'Person cannot be deleted because they are still linked to database data.'
|
||||
)
|
||||
expect(deleteResult.body.blockers).toEqual([
|
||||
'person has data update history',
|
||||
'person is assigned to projects or coordinator groups',
|
||||
])
|
||||
|
||||
const person = await nowDb.com_people.findUnique({ where: { initials: 'AD' } })
|
||||
expect(person).not.toBeNull()
|
||||
})
|
||||
|
||||
it('does not allow users to delete their own person record', async () => {
|
||||
const deleteResult = await send<{ blockers: string[]; message: string }>('person/TEST-SU', 'DELETE')
|
||||
|
||||
expect(deleteResult.status).toEqual(409)
|
||||
expect(deleteResult.body.message).toEqual('You cannot delete your own person record.')
|
||||
expect(deleteResult.body.blockers).toEqual(['self'])
|
||||
})
|
||||
|
||||
it('deleting fails without admin permissions', async () => {
|
||||
await createPerson('DEL-PERM')
|
||||
|
||||
logout()
|
||||
const deleteResultNoPerm = await send('person/DEL-PERM', 'DELETE')
|
||||
expect(deleteResultNoPerm.status).toEqual(403)
|
||||
|
||||
await login('testEr')
|
||||
const deleteResultEr = await send('person/DEL-PERM', 'DELETE')
|
||||
expect(deleteResultEr.status).toEqual(403)
|
||||
expect(deleteResultEr.body).toEqual(noPermError)
|
||||
|
||||
const person = await nowDb.com_people.findUnique({ where: { initials: 'DEL-PERM' } })
|
||||
expect(person).not.toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Router, Request } from 'express'
|
||||
import { getAllPersons, getPersonDetails, validateEntirePerson } from '../services/person'
|
||||
import { deletePerson, getAllPersons, getPersonDetails, validateEntirePerson } from '../services/person'
|
||||
import {
|
||||
Role,
|
||||
PersonDetailsType,
|
||||
@@ -73,4 +73,14 @@ router.put(
|
||||
}
|
||||
)
|
||||
|
||||
router.delete('/:id', requireOneOf([Role.Admin]), async (req, res) => {
|
||||
const result = await deletePerson(req.params.id, req.user!.initials)
|
||||
|
||||
if (!result.deleted) {
|
||||
return res.status(result.status).send({ message: result.message, blockers: result.blockers })
|
||||
}
|
||||
|
||||
return res.status(200).send()
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
@@ -4,6 +4,9 @@ import Prisma from '../../prisma/generated/now_test_client'
|
||||
import { ValidationObject } from '../../../frontend/src/shared/validators/validator'
|
||||
import { validatePerson } from '../../../frontend/src/shared/validators/person'
|
||||
|
||||
export const PERSON_DELETE_BLOCKED_MESSAGE = 'Person cannot be deleted because they are still linked to database data.'
|
||||
export const PERSON_DELETE_SELF_MESSAGE = 'You cannot delete your own person record.'
|
||||
|
||||
export const getAllPersons = async () => {
|
||||
const persons = await nowDb.com_people.findMany({})
|
||||
const users = await nowDb.com_users.findMany({})
|
||||
@@ -39,3 +42,72 @@ export const validateEntirePerson = (editedFields: EditDataType<Prisma.com_peopl
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
const hasAny = async (checks: Array<Promise<number>>): Promise<boolean> => {
|
||||
const counts = await Promise.all(checks)
|
||||
return counts.some(count => count > 0)
|
||||
}
|
||||
|
||||
const hasUpdateActivity = async (initials: string): Promise<boolean> => {
|
||||
return hasAny([
|
||||
nowDb.now_lau.count({ where: { OR: [{ lau_coordinator: initials }, { lau_authorizer: initials }] } }),
|
||||
nowDb.now_sau.count({ where: { OR: [{ sau_coordinator: initials }, { sau_authorizer: initials }] } }),
|
||||
nowDb.now_tau.count({ where: { OR: [{ tau_coordinator: initials }, { tau_authorizer: initials }] } }),
|
||||
nowDb.now_bau.count({ where: { OR: [{ bau_coordinator: initials }, { bau_authorizer: initials }] } }),
|
||||
nowDb.now_time_update.count({ where: { OR: [{ coordinator: initials }, { authorizer: initials }] } }),
|
||||
])
|
||||
}
|
||||
|
||||
const hasCoordinationOrProjectLinks = async (initials: string): Promise<boolean> => {
|
||||
return hasAny([
|
||||
nowDb.now_proj.count({ where: { contact: initials } }),
|
||||
nowDb.now_proj_people.count({ where: { initials } }),
|
||||
nowDb.now_reg_coord_people.count({ where: { initials } }),
|
||||
nowDb.now_sp_coord_people.count({ where: { initials } }),
|
||||
nowDb.now_strat_coord_people.count({ where: { initials } }),
|
||||
])
|
||||
}
|
||||
|
||||
export const getPersonDeleteBlockers = async (initials: string): Promise<string[]> => {
|
||||
const blockers: string[] = []
|
||||
|
||||
if (await hasUpdateActivity(initials)) {
|
||||
blockers.push('person has data update history')
|
||||
}
|
||||
|
||||
if (await hasCoordinationOrProjectLinks(initials)) {
|
||||
blockers.push('person is assigned to projects or coordinator groups')
|
||||
}
|
||||
|
||||
return blockers
|
||||
}
|
||||
|
||||
export const deletePerson = async (initials: string, currentUserInitials: string) => {
|
||||
if (initials === currentUserInitials) {
|
||||
return { deleted: false, status: 409 as const, message: PERSON_DELETE_SELF_MESSAGE, blockers: ['self'] }
|
||||
}
|
||||
|
||||
const person = await nowDb.com_people.findUnique({
|
||||
where: { initials },
|
||||
select: { initials: true, user_id: true },
|
||||
})
|
||||
|
||||
if (!person) {
|
||||
return { deleted: false, status: 404 as const, message: 'Person not found.', blockers: [] }
|
||||
}
|
||||
|
||||
const blockers = await getPersonDeleteBlockers(initials)
|
||||
if (blockers.length > 0) {
|
||||
return { deleted: false, status: 409 as const, message: PERSON_DELETE_BLOCKED_MESSAGE, blockers }
|
||||
}
|
||||
|
||||
await nowDb.$transaction(async prisma => {
|
||||
await prisma.com_people.delete({ where: { initials } })
|
||||
|
||||
if (person.user_id) {
|
||||
await prisma.com_users.deleteMany({ where: { user_id: person.user_id } })
|
||||
}
|
||||
})
|
||||
|
||||
return { deleted: true, status: 200 as const, message: null, blockers: [] }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user