diff --git a/src/__tests__/areas.ts b/src/__tests__/areas.ts index e57b11f..66d6361 100644 --- a/src/__tests__/areas.ts +++ b/src/__tests__/areas.ts @@ -168,5 +168,28 @@ describe('areas API', () => { expect(response.statusCode).toBe(200) }) + + it('should allow calling of the setAreaParent gql endpoint.', async () => { + const testArea = await areas.addArea(muuid.from(userUuid), 'A Rolling Stone', usa.metadata.area_id) + + const response = await queryAPI({ + query: ` + mutation SetAreaParent($area: ID!, $newParent: ID!) { + setAreaParent(area: $area, newParent: $newParent) { + areaName + area_name + } + } + `, + operationName: 'SetAreaParent', + userUuid, + app, + // Move it to canada + variables: { area: testArea.metadata.area_id, newParent: ca.metadata.area_id } + }) + + console.log(response.body) + expect(response.statusCode).toBe(200) + }) }) }) diff --git a/src/graphql/area/AreaMutations.ts b/src/graphql/area/AreaMutations.ts index b454fda..f598cfe 100644 --- a/src/graphql/area/AreaMutations.ts +++ b/src/graphql/area/AreaMutations.ts @@ -4,6 +4,7 @@ import { AreaType } from '../../db/AreaTypes.js' import { ContextWithAuth } from '../../types.js' import type MutableAreaDataSource from '../../model/MutableAreaDataSource.js' import { BulkImportInputType, BulkImportResultType } from '../../db/BulkImportTypes.js' +import { UserInputError } from 'apollo-server-core' const AreaMutations = { @@ -70,6 +71,23 @@ const AreaMutations = { ) }, + setAreaParent: async (_, { input }, { dataSources, user }: ContextWithAuth): Promise => { + const { areas } = dataSources + + if (user?.uuid == null) throw new UserInputError('Missing user uuid') + if (input?.area == null) throw new UserInputError('Missing area uuid') + if (input?.newParent == null) throw new UserInputError('Missing area new parent uuid') + + const areaUuid = muuid.from(input.uuid) + const newParentUuid = muuid.from(input.newParent) + + return await areas.setAreaParent( + user.uuid, + areaUuid, + newParentUuid + ) + }, + updateAreasSortingOrder: async (_, { input }, { dataSources, user }: ContextWithAuth): Promise => { const { areas } = dataSources diff --git a/src/graphql/schema/AreaEdit.gql b/src/graphql/schema/AreaEdit.gql index 5380857..9a3b17d 100644 --- a/src/graphql/schema/AreaEdit.gql +++ b/src/graphql/schema/AreaEdit.gql @@ -4,6 +4,24 @@ type Mutation { """ addArea(input: AreaInput): Area + """ + Move an area from one parent to another. + + When areas are created, an initial parent must be assigned. this is to prevent a buildup of floating orphan + nodes that haven't been organized. Nevertheless, you may find that an area and its children may need to be + re-organized. This could happen for any number of reasons, but when it does need to happen you will need to + tap this mutation to update the areas parent. + + When you migrate an area, it will move (along with all the sub-areas inside it) to the targeted area. + The best way to conceptualize this is as a directory, so the usual rules will apply. If you could not + create the area as a child of the target (for example, because a name is already taken), then you should + not reasonably be able to move the area into this new parent. + + Caveats + - This mutation does not affect countries + """ + setAreaParent(area: ID!, newParent: ID!): Area + """ Update area attributes """ diff --git a/src/model/MutableAreaDataSource.ts b/src/model/MutableAreaDataSource.ts index c55730a..de3a586 100644 --- a/src/model/MutableAreaDataSource.ts +++ b/src/model/MutableAreaDataSource.ts @@ -454,6 +454,9 @@ export default class MutableAreaDataSource extends AreaDataSource { throw new AreaStructureError('CIRCULAR STRUCTURE: The requested parent is already a descendant, and so cannot also be a parent.') } + // the name of the area being moved into this area must be unique in its new context + await this.validateUniqueAreaName(area.area_name, nextParent) + // By this point we are satisfied that there are no obvious reasons to reject this request, so we can begin saving // and producing effects in the context of this transaction. area.parent = nextParent._id diff --git a/src/model/__tests__/MutableAreaDataSource.test.ts b/src/model/__tests__/MutableAreaDataSource.test.ts index 34a703a..7ef307a 100644 --- a/src/model/__tests__/MutableAreaDataSource.test.ts +++ b/src/model/__tests__/MutableAreaDataSource.test.ts @@ -199,14 +199,26 @@ describe("Test area mutations", () => { })) describe("cases for changing an areas parent",() => { - test('Can update an areas parent reference', async () => addArea() - .then(parent => addArea(undefined, { parent })) - .then(async area => { - let otherArea = await addArea() - await areas.setAreaParent(testUser, area.metadata.area_id, otherArea.metadata.area_id) - expect(area.parent).toBeDefined() - expect(area.parent!.equals(otherArea._id)) - })) + test('Can update an areas parent reference', async () => addArea() + .then(parent => addArea(undefined, { parent })) + .then(async area => { + let otherArea = await addArea() + await areas.setAreaParent(testUser, area.metadata.area_id, otherArea.metadata.area_id) + expect(area.parent).toBeDefined() + expect(area.parent!.equals(otherArea._id)) + })) + + test('Updating an area will not produce duplicate named children in the target', async () => addArea() + .then(async parent => addArea(undefined, { parent })) + .then(async area => { + let otherArea = await addArea() + // put a duplicate name inside otherArea + addArea(area.area_name, { parent: otherArea }) + + await expect( + () => areas.setAreaParent(testUser, area.metadata.area_id, otherArea.metadata.area_id) + ).rejects.toThrowError(UserInputError) + })) test('Updating an areas parents reference to the one already specified should throw', async () => addArea() .then(async parent => [ await addArea(undefined, { parent }), parent])