diff --git a/src/auth/permissions.ts b/src/auth/permissions.ts index 4f6dfbb8..1b892485 100644 --- a/src/auth/permissions.ts +++ b/src/auth/permissions.ts @@ -11,6 +11,7 @@ const permissions = shield({ removeArea: isEditor, addArea: isEditor, updateArea: isEditor, + updateClimb: isEditor, updateClimbs: isEditor, deleteClimbs: isEditor, bulkImportAreas: isEditor, diff --git a/src/graphql/climb/ClimbMutations.ts b/src/graphql/climb/ClimbMutations.ts index f3168da4..c89492d0 100644 --- a/src/graphql/climb/ClimbMutations.ts +++ b/src/graphql/climb/ClimbMutations.ts @@ -1,5 +1,6 @@ import muid, { MUUID } from 'uuid-mongodb' import { ContextWithAuth } from '../../types.js' +import { ClimbType } from '../../db/ClimbTypes.js' const ClimbMutations = { updateClimbs: async (_, { input }, { dataSources, user }: ContextWithAuth): Promise => { @@ -11,6 +12,15 @@ const ClimbMutations = { return await ds.addOrUpdateClimbs(user.uuid, muid.from(parentId), changes) }, + updateClimb: async (_, { input }, { dataSources, user }: ContextWithAuth): Promise => { + const { climbs: ds } = dataSources + const { id, ...changes } = input + + if (user?.uuid == null) throw new Error('Missing user uuid') + + return await ds.updateClimbById(user.uuid, muid.from(id), changes) + }, + deleteClimbs: async (_, { input }, { dataSources, user }: ContextWithAuth): Promise => { const { climbs: ds } = dataSources diff --git a/src/graphql/schema/ClimbEdit.gql b/src/graphql/schema/ClimbEdit.gql index e1f36b80..66f56f24 100644 --- a/src/graphql/schema/ClimbEdit.gql +++ b/src/graphql/schema/ClimbEdit.gql @@ -4,6 +4,11 @@ type Mutation { """ updateClimbs(input: UpdateClimbsInput): [ID] + """ + Update a single climb by its ID. Unlike updateClimbs, this doesn't require the parent area ID. + """ + updateClimb(input: SingleClimbInput!): Climb + """ Delete one or more climbs """ @@ -49,6 +54,30 @@ input SingleClimbChangeInput { experimentalAuthor: ExperimentalAuthorType } +""" +Input for updating a single climb by its ID. The climb must already exist. +""" +input SingleClimbInput { + "Climb UUID (required)" + id: ID! + name: String + disciplines: DisciplineType + grade: String + leftRightIndex: Int + description: String + location: String + protection: String + "Legacy FA data" + fa: String + "Length in meters" + length: Int + "Number of fixed anchors" + boltsCount: Int + "List of Pitch objects representing individual pitches of a multi-pitch climb" + pitches: [PitchInput] + experimentalAuthor: ExperimentalAuthorType +} + input GradeTypeInput { vscale: String yds: String diff --git a/src/model/MutableClimbDataSource.ts b/src/model/MutableClimbDataSource.ts index 6fa12827..8e00a73e 100644 --- a/src/model/MutableClimbDataSource.ts +++ b/src/model/MutableClimbDataSource.ts @@ -285,6 +285,39 @@ export default class MutableClimbDataSource extends ClimbDataSource { } } + /** + * Update a single climb by its ID. Unlike addOrUpdateClimbs, this doesn't require the parent area ID. + * @param userId User performing the action + * @param climbId The climb's own ID + * @param changes The fields to update + * @returns The updated climb, or null if not found + */ + async updateClimbById (userId: MUUID, climbId: MUUID, changes: Omit): Promise { + // Look up the climb to get its parent area ID + const climb = await this.climbModel.findOne({ _id: climbId, _deleting: { $eq: null } }).lean() + + if (climb == null) { + throw new GraphQLError(`Climb with id: ${climbId.toUUID().toString()} not found`, { + extensions: { + code: ApolloServerErrorCode.BAD_USER_INPUT + } + }) + } + + const parentId = climb.metadata.areaRef + + // Use existing logic with the climb ID included + const changeWithId: ClimbChangeInputType = { + ...changes, + id: climbId.toUUID().toString() + } + + await this.addOrUpdateClimbs(userId, parentId, [changeWithId]) + + // Return the updated climb + return await this.climbModel.findOne({ _id: climbId }).lean() + } + /** * Delete one or more climbs by climb ID. * @param userId User performing the action