diff --git a/src/__tests__/areas.ts b/src/__tests__/areas.ts index dcce766d..5058a7f2 100644 --- a/src/__tests__/areas.ts +++ b/src/__tests__/areas.ts @@ -2,8 +2,10 @@ import { ApolloServer } from 'apollo-server' import muuid from 'uuid-mongodb' import { jest } from '@jest/globals' import MutableAreaDataSource, { createInstance as createAreaInstance } from '../model/MutableAreaDataSource.js' +import { createInstance as createClimbInstance } from '../model/MutableClimbDataSource.js' import MutableOrganizationDataSource, { createInstance as createOrgInstance } from '../model/MutableOrganizationDataSource.js' import { AreaType } from '../db/AreaTypes.js' +import { ClimbChangeInputType } from '../db/ClimbTypes.js' import { OrgType, OrganizationType, OrganizationEditableFieldsType } from '../db/OrganizationTypes.js' import { queryAPI, setUpServer } from '../utils/testUtils.js' import { muuidToString } from '../utils/helpers.js' @@ -22,6 +24,7 @@ describe('areas API', () => { let usa: AreaType let ca: AreaType let wa: AreaType + let ak: AreaType beforeAll(async () => { ({ server, inMemoryDB } = await setUpServer()) @@ -38,6 +41,7 @@ describe('areas API', () => { usa = await areas.addCountry('usa') ca = await areas.addArea(user, 'CA', usa.metadata.area_id) wa = await areas.addArea(user, 'WA', usa.metadata.area_id) + ak = await areas.addArea(user, 'AK', usa.metadata.area_id) }) afterAll(async () => { @@ -45,6 +49,57 @@ describe('areas API', () => { await inMemoryDB.close() }) + describe('mutations', () => { + it('updates sorting order of subareas and queries returns them in order', async () => { + const updateSortingOrderQuery = ` + mutation ($input: [AreaSortingInput]) { + updateAreasSortingOrder(input: $input) + } + ` + const updateResponse = await queryAPI({ + query: updateSortingOrderQuery, + variables: { + input: [ + { areaId: wa.metadata.area_id, leftRightIndex: 3 }, + { areaId: ca.metadata.area_id, leftRightIndex: 0 }, + { areaId: ak.metadata.area_id, leftRightIndex: 10 } + ] + }, + userUuid + }) + + expect(updateResponse.statusCode).toBe(200) + const sortingOrderResult = updateResponse.body.data.updateAreasSortingOrder + expect(sortingOrderResult).toHaveLength(3) + + const areaChildrenQuery = ` + query area($input: ID) { + area(uuid: $input) { + children { + uuid + metadata { + leftRightIndex + } + } + } + } + ` + + const areaChildrenResponse = await queryAPI({ + query: areaChildrenQuery, + variables: { input: usa.metadata.area_id }, + userUuid + }) + + expect(areaChildrenResponse.statusCode).toBe(200) + const areaResult = areaChildrenResponse.body.data.area + // In leftRightIndex order + expect(areaResult.children[0]).toMatchObject({ uuid: muuidToString(ca.metadata.area_id), metadata: { leftRightIndex: 0 } }) + expect(areaResult.children[1]).toMatchObject({ uuid: muuidToString(wa.metadata.area_id), metadata: { leftRightIndex: 3 } }) + expect(areaResult.children[2]).toMatchObject({ uuid: muuidToString(ak.metadata.area_id), metadata: { leftRightIndex: 10 } }) + }) + }) + describe('queries', () => { const areaQuery = ` query area($input: ID) { @@ -101,5 +156,56 @@ describe('areas API', () => { // ca and so should not be listed. expect(areaResult.organizations).toHaveLength(0) }) + + it('returns climbs in leftRightIndex order', async () => { + const climbs = createClimbInstance() + const leftRoute: ClimbChangeInputType = { + name: 'left', + disciplines: { sport: true }, + description: 'Leftmost route on the wall', + leftRightIndex: 0 + } + const middleRoute: ClimbChangeInputType = { + name: 'middle', + disciplines: { sport: true }, + description: 'Middle route on the wall', + leftRightIndex: 1 + } + const rightRoute: ClimbChangeInputType = { + name: 'right', + disciplines: { sport: true }, + description: 'Rightmost route on the wall', + leftRightIndex: 2 + } + await climbs.addOrUpdateClimbs( + user, + ca.metadata.area_id, + [middleRoute, leftRoute, rightRoute] + ) + + const areaClimbsQuery = ` + query area($input: ID) { + area(uuid: $input) { + climbs { + name + metadata { + leftRightIndex + } + } + } + } + ` + const areaClimbsResponse = await queryAPI({ + query: areaClimbsQuery, + variables: { input: ca.metadata.area_id }, + userUuid + }) + expect(areaClimbsResponse.statusCode).toBe(200) + const areaResult = areaClimbsResponse.body.data.area + // In leftRightIndex order + expect(areaResult.climbs[0]).toMatchObject({ name: 'left', metadata: { leftRightIndex: 0 } }) + expect(areaResult.climbs[1]).toMatchObject({ name: 'middle', metadata: { leftRightIndex: 1 } }) + expect(areaResult.climbs[2]).toMatchObject({ name: 'right', metadata: { leftRightIndex: 2 } }) + }) }) }) diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts index 58f2c8d4..2e065d0c 100644 --- a/src/graphql/resolvers.ts +++ b/src/graphql/resolvers.ts @@ -200,13 +200,6 @@ const resolvers = { // New camel case field areaName: async (node: AreaType) => node.area_name, - children: async (parent: AreaType, _, { dataSources: { areas } }: Context) => { - if (parent.children.length > 0) { - return await areas.findManyByIds(parent.children) - } - return [] - }, - aggregate: async (node: AreaType) => { return node.aggregate }, diff --git a/src/model/AreaDataSource.ts b/src/model/AreaDataSource.ts index 1e9e8c00..537157e5 100644 --- a/src/model/AreaDataSource.ts +++ b/src/model/AreaDataSource.ts @@ -2,12 +2,13 @@ import { MongoDataSource } from 'apollo-datasource-mongodb' import { Filter } from 'mongodb' import muuid from 'uuid-mongodb' import bboxPolygon from '@turf/bbox-polygon' +import { Types as mongooseTypes } from 'mongoose' import { getAreaModel, getMediaModel, getMediaObjectModel } from '../db/index.js' import { AreaType } from '../db/AreaTypes' import { GQLFilter, AreaFilterParams, PathTokenParams, LeafStatusParams, ComparisonFilterParams, StatisticsType, CragsNear, BBoxType } from '../types' import { getClimbModel } from '../db/ClimbSchema.js' -import { ClimbGQLQueryType } from '../db/ClimbTypes.js' +import { ClimbGQLQueryType, ClimbType } from '../db/ClimbTypes.js' import { logger } from '../logger.js' export default class AreaDataSource extends MongoDataSource { @@ -104,10 +105,24 @@ export default class AreaDataSource extends MongoDataSource { as: 'climbs' // clobber array of climb IDs with climb objects } }, + { // Self-join to populate children areas. + $lookup: { + from: 'areas', + localField: 'children', + foreignField: '_id', + as: 'children' + } + }, { $set: { 'climbs.gradeContext': '$gradeContext' // manually set area's grade context to climb } + }, + { + $set: { + climbs: { $sortArray: { input: '$climbs', sortBy: { 'metadata.left_right_index': 1 } } }, + children: { $sortArray: { input: '$children', sortBy: { 'metadata.leftRightIndex': 1 } } } + } } ]) @@ -117,7 +132,7 @@ export default class AreaDataSource extends MongoDataSource { throw new Error(`Area ${uuid.toUUID().toString()} not found.`) } - async findManyClimbsByUuids (uuidList: muuid.MUUID[]): Promise { + async findManyClimbsByUuids (uuidList: muuid.MUUID[]): Promise { const rs = await this.climbModel.find().where('_id').in(uuidList) return rs }