mirror of
https://github.com/nowcommunity/nowdatabase.git
synced 2026-10-12 19:13:52 +03:00
Merge pull request #674 from nowcommunity/backend-functionality-for-editing-persons
Backend functionality for editing persons
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
import { PersonDetailsType } from '../../../../frontend/src/shared/types'
|
||||
|
||||
export const existingPerson: Omit<PersonDetailsType, 'user'> = {
|
||||
initials: 'AD',
|
||||
first_name: 'adf',
|
||||
surname: 'ads',
|
||||
full_name: 'adf ads',
|
||||
format: null,
|
||||
email: 'email',
|
||||
user_id: 156,
|
||||
organization: 'organization',
|
||||
country: 'Finland',
|
||||
password_set: new Date('2024-05-22T00:00:00.000Z'),
|
||||
used_morph: null,
|
||||
used_now: null,
|
||||
used_gene: null,
|
||||
}
|
||||
|
||||
export const editedPerson = {
|
||||
initials: existingPerson.initials,
|
||||
first_name: 'Test first name',
|
||||
surname: 'Test surname',
|
||||
email: 'test.email@emailprovider.com',
|
||||
organization: 'Test organization',
|
||||
country: 'Test country',
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it } from '@jest/globals'
|
||||
import { PersonDetailsType } from '../../../../frontend/src/shared/types'
|
||||
import { pool } from '../../utils/db'
|
||||
import { login, logout, noPermError, resetDatabase, resetDatabaseTimeout, send } from '../utils'
|
||||
import { editedPerson, existingPerson } from './data'
|
||||
|
||||
let updatedPerson: PersonDetailsType | null = null
|
||||
|
||||
describe('Updating person works', () => {
|
||||
beforeAll(async () => {
|
||||
await resetDatabase()
|
||||
}, resetDatabaseTimeout)
|
||||
beforeEach(async () => {
|
||||
await login()
|
||||
})
|
||||
afterAll(async () => {
|
||||
await pool.end()
|
||||
})
|
||||
|
||||
it('Request succeeds and returns valid initials', async () => {
|
||||
const { body: resultBody, status: getReqStatus } = await send<{ initials: string }>('person/', 'PUT', {
|
||||
person: editedPerson,
|
||||
})
|
||||
const { initials: resultId } = resultBody
|
||||
|
||||
expect(typeof resultId).toEqual('string')
|
||||
expect(getReqStatus).toEqual(200)
|
||||
|
||||
const { body, status: getReqStat } = await send<PersonDetailsType>(`person/${resultId}`, 'GET')
|
||||
expect(getReqStat).toEqual(200)
|
||||
updatedPerson = body
|
||||
})
|
||||
|
||||
it('Contains correct data and full name is automatically updated', () => {
|
||||
const { initials, first_name, surname, full_name, email, organization, country } = updatedPerson!
|
||||
expect(initials).toEqual(existingPerson.initials)
|
||||
expect(first_name).toEqual(editedPerson.first_name)
|
||||
expect(surname).toEqual(editedPerson.surname)
|
||||
expect(email).toEqual(editedPerson.email)
|
||||
expect(organization).toEqual(editedPerson.organization)
|
||||
expect(country).toEqual(editedPerson.country)
|
||||
|
||||
expect(full_name).toEqual(`${editedPerson.first_name} ${editedPerson.surname}`)
|
||||
})
|
||||
|
||||
it('Updating fails if any person field is empty', async () => {
|
||||
const { body: resultBody1, status: getReqStatus1 } = await send('person/', 'PUT', {
|
||||
person: { ...editedPerson, first_name: '' },
|
||||
})
|
||||
expect(getReqStatus1).toEqual(403)
|
||||
expect(resultBody1.length).toEqual(1) //There should be 1 validation error
|
||||
|
||||
const { body: resultBody2, status: getReqStatus2 } = await send('person/', 'PUT', {
|
||||
person: { ...editedPerson, surname: '' },
|
||||
})
|
||||
expect(getReqStatus2).toEqual(403)
|
||||
expect(resultBody2.length).toEqual(1) //There should be 1 validation error
|
||||
|
||||
const { body: resultBody3, status: getReqStatus3 } = await send('person/', 'PUT', {
|
||||
person: { ...editedPerson, email: '' },
|
||||
})
|
||||
expect(getReqStatus3).toEqual(403)
|
||||
expect(resultBody3.length).toEqual(1) //There should be 1 validation error
|
||||
|
||||
const { body: resultBody4, status: getReqStatus4 } = await send('person/', 'PUT', {
|
||||
person: { ...editedPerson, organization: '' },
|
||||
})
|
||||
expect(getReqStatus4).toEqual(403)
|
||||
expect(resultBody4.length).toEqual(1) //There should be 1 validation error
|
||||
|
||||
const { body: resultBody5, status: getReqStatus5 } = await send('person/', 'PUT', {
|
||||
person: { ...editedPerson, country: '' },
|
||||
})
|
||||
expect(getReqStatus5).toEqual(403)
|
||||
expect(resultBody5.length).toEqual(1) //There should be 1 validation error
|
||||
})
|
||||
|
||||
it('Updating other people fails without permissions', async () => {
|
||||
logout()
|
||||
const { body: resultBodyNoPerm, status: resultStatusNoPerm } = await send('person/', 'PUT', {
|
||||
person: { initials: 'AD', first_name: 'Test first name 2' },
|
||||
})
|
||||
expect(resultBodyNoPerm).toEqual(noPermError)
|
||||
expect(resultStatusNoPerm).toEqual(401)
|
||||
|
||||
await login('testEr')
|
||||
const { body: resultBodyEr, status: resultStatusEr } = await send('person/', 'PUT', {
|
||||
person: { initials: 'AD', first_name: 'Test first name 2' },
|
||||
})
|
||||
expect(resultBodyEr).toEqual(noPermError)
|
||||
expect(resultStatusEr).toEqual(401)
|
||||
|
||||
await login('testEu')
|
||||
const { body: resultBodyEu, status: resultStatusEu } = await send('person/', 'PUT', {
|
||||
person: { initials: 'AD', first_name: 'Test first name 2' },
|
||||
})
|
||||
expect(resultBodyEu).toEqual(noPermError)
|
||||
expect(resultStatusEu).toEqual(401)
|
||||
})
|
||||
|
||||
it('Non-admin user updating their own information succeeds', async () => {
|
||||
await login('testEr')
|
||||
const { body: resultBodyEr, status: resultStatusEr } = await send<{ initials: string }>('person/', 'PUT', {
|
||||
person: { initials: 'TEST-ER', first_name: 'Test first name 3' },
|
||||
})
|
||||
const { initials: resultId } = resultBodyEr
|
||||
|
||||
expect(typeof resultId).toEqual('string')
|
||||
expect(resultStatusEr).toEqual(200)
|
||||
|
||||
const { body, status: getReqStat } = await send<PersonDetailsType>(`person/${resultId}`, 'GET')
|
||||
expect(getReqStat).toEqual(200)
|
||||
expect(body.first_name).toEqual('Test first name 3')
|
||||
})
|
||||
})
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Router } from 'express'
|
||||
import { getAllPersons, getPersonDetails } from '../services/person'
|
||||
import { Role } from '../../../frontend/src/shared/types'
|
||||
import { Router, Request } from 'express'
|
||||
import { getAllPersons, getPersonDetails, validateEntirePerson } from '../services/person'
|
||||
import { Role, PersonDetailsType, EditDataType, EditMetaData } from '../../../frontend/src/shared/types'
|
||||
import { requireOneOf } from '../middlewares/authorizer'
|
||||
import { writePerson } from '../services/write/person'
|
||||
|
||||
const router = Router()
|
||||
|
||||
@@ -14,12 +15,46 @@ router.get('/:id', async (req, res) => {
|
||||
const id = req.params.id
|
||||
|
||||
/* Access checking happens differently for this route, since we want to allow the route to return the user's own data for any user */
|
||||
if (!req.user) return res.status(401).send()
|
||||
if (req.user.role !== Role.Admin && req.user.initials !== id) return res.status(401).send()
|
||||
if (!req.user)
|
||||
return res.status(401).send({
|
||||
message: 'User is not logged in',
|
||||
})
|
||||
if (req.user.role !== Role.Admin && req.user.initials !== id)
|
||||
return res.status(401).send({
|
||||
message: 'User not authorized for the requested resource or action',
|
||||
})
|
||||
|
||||
const person = await getPersonDetails(id)
|
||||
if (!person) return res.status(404).send()
|
||||
return res.status(200).send(person)
|
||||
})
|
||||
|
||||
router.put(
|
||||
'/',
|
||||
async (req: Request<object, object, { person: EditDataType<PersonDetailsType> & EditMetaData }>, res) => {
|
||||
const { ...editedPerson } = req.body.person
|
||||
|
||||
if (!editedPerson.initials) {
|
||||
return res.status(403).send({ error: 'Missing initials, creating new persons is not yet implemented' })
|
||||
}
|
||||
|
||||
/* Access checking happens differently for this route, since we want to allow users to modify their own data */
|
||||
if (!req.user)
|
||||
return res.status(401).send({
|
||||
message: 'User not authorized for the requested resource or action',
|
||||
})
|
||||
if (req.user.role !== Role.Admin && req.user.initials !== editedPerson.initials)
|
||||
return res.status(401).send({
|
||||
message: 'User not authorized for the requested resource or action',
|
||||
})
|
||||
|
||||
const validationErrors = validateEntirePerson({ ...editedPerson })
|
||||
if (validationErrors.length > 0) {
|
||||
return res.status(403).send(validationErrors)
|
||||
}
|
||||
const initials = await writePerson(editedPerson)
|
||||
return res.status(200).send({ initials })
|
||||
}
|
||||
)
|
||||
|
||||
export default router
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { nowDb } from '../utils/db'
|
||||
import { EditDataType, PersonDetailsType } from '../../../frontend/src/shared/types'
|
||||
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 getAllPersons = async () => {
|
||||
const persons = await nowDb.com_people.findMany({})
|
||||
@@ -25,3 +29,13 @@ export const getPersonDetails = async (id: string) => {
|
||||
|
||||
return { ...person, user }
|
||||
}
|
||||
|
||||
export const validateEntirePerson = (editedFields: EditDataType<Prisma.com_people>) => {
|
||||
const keys = Object.keys(editedFields)
|
||||
const errors: ValidationObject[] = []
|
||||
for (const key of keys) {
|
||||
const error = validatePerson(editedFields, key as keyof PersonDetailsType)
|
||||
if (error.error) errors.push(error)
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { EditDataType, PersonDetailsType } from '../../../../frontend/src/shared/types'
|
||||
import Prisma from '../../../prisma/generated/now_test_client'
|
||||
import { nowDb, getFieldsOfTables } from '../../utils/db'
|
||||
import { filterAllowedKeys } from './writeOperations/utils'
|
||||
|
||||
export const writePerson = async (person: EditDataType<PersonDetailsType>) => {
|
||||
const allowedColumns = getFieldsOfTables(['com_people'])
|
||||
const filteredPerson = filterAllowedKeys(person, allowedColumns) as Prisma.com_people
|
||||
let personId: string
|
||||
|
||||
if (!filteredPerson.initials) {
|
||||
throw new Error('Missing initials, creating new persons is not yet implemented')
|
||||
} else {
|
||||
await nowDb.com_people.update({
|
||||
where: { initials: person.initials },
|
||||
data: { ...filteredPerson, full_name: `${filteredPerson.first_name} ${filteredPerson.surname}` },
|
||||
})
|
||||
personId = filteredPerson.initials
|
||||
}
|
||||
|
||||
return personId
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { EditDataType, PersonDetailsType } from '../types'
|
||||
import { Validators, validator } from './validator'
|
||||
|
||||
export const validatePerson = (
|
||||
editData: EditDataType<PersonDetailsType>,
|
||||
fieldName: keyof EditDataType<PersonDetailsType>
|
||||
) => {
|
||||
const validators: Validators<Partial<EditDataType<PersonDetailsType>>> = {
|
||||
initials: {
|
||||
name: 'initials',
|
||||
required: true,
|
||||
},
|
||||
first_name: {
|
||||
name: 'First Name',
|
||||
required: true,
|
||||
},
|
||||
surname: {
|
||||
name: 'Surname',
|
||||
required: true,
|
||||
},
|
||||
email: {
|
||||
name: 'Email',
|
||||
required: true,
|
||||
},
|
||||
organization: {
|
||||
name: 'Organization',
|
||||
required: true,
|
||||
},
|
||||
country: {
|
||||
name: 'Country',
|
||||
required: true,
|
||||
},
|
||||
}
|
||||
|
||||
return validator<EditDataType<PersonDetailsType>>(validators, editData, fieldName)
|
||||
}
|
||||
Reference in New Issue
Block a user