diff --git a/src/db/AreaTypes.ts b/src/db/AreaTypes.ts index 1cb09909..7a207af7 100644 --- a/src/db/AreaTypes.ts +++ b/src/db/AreaTypes.ts @@ -5,6 +5,7 @@ import { BBox, Point } from '@turf/helpers' import { ClimbType } from './ClimbTypes.js' import { ChangeRecordMetadataType } from './ChangeLogType.js' import { GradeContexts } from '../GradeUtils.js' +import { ExperimentalAuthorType } from './UserTypes.js' /** * Areas are a grouping mechanism in the OpenBeta data model that allow @@ -165,6 +166,7 @@ export interface AreaEditableFieldsType { shortCode?: string lat?: number lng?: number + experimentalAuthor?: ExperimentalAuthorType } export interface CountByGroupType { diff --git a/src/db/UserTypes.ts b/src/db/UserTypes.ts index 15a6645c..5f625feb 100644 --- a/src/db/UserTypes.ts +++ b/src/db/UserTypes.ts @@ -8,3 +8,8 @@ export interface ExperimentalUserType { createdAt: Date updatedAt: Date } + +export interface ExperimentalAuthorType { + displayName: string + url: string +} diff --git a/src/graphql/area/AreaMutations.ts b/src/graphql/area/AreaMutations.ts index 086b1e9f..20177672 100644 --- a/src/graphql/area/AreaMutations.ts +++ b/src/graphql/area/AreaMutations.ts @@ -29,7 +29,7 @@ const AreaMutations = { addArea: async (_, { input }, { dataSources, user }: ContextWithAuth): Promise => { const { areas } = dataSources - const { name, parentUuid, countryCode } = input + const { name, parentUuid, countryCode, experimentalAuthor } = input // permission middleware shouldn't send undefined uuid if (user?.uuid == null) throw new Error('Missing user uuid') @@ -37,7 +37,9 @@ const AreaMutations = { return await areas.addArea( user.uuid, name, parentUuid == null ? null : muuid.from(parentUuid), - countryCode) + countryCode, + experimentalAuthor + ) }, updateArea: async (_, { input }, { dataSources, user }: ContextWithAuth): Promise => { diff --git a/src/graphql/schema/AreaEdit.gql b/src/graphql/schema/AreaEdit.gql index c8c562dd..b8235132 100644 --- a/src/graphql/schema/AreaEdit.gql +++ b/src/graphql/schema/AreaEdit.gql @@ -28,6 +28,7 @@ input AreaInput { parentUuid: ID countryCode: String isDestination: Boolean + experimentalAuthor: ExperimentalAuthorType } input RemoveAreaInput { @@ -44,4 +45,5 @@ input AreEditableFieldsInput { lat: Float lng: Float description: String + experimentalAuthor: ExperimentalAuthorType } diff --git a/src/model/MutableAreaDataSource.ts b/src/model/MutableAreaDataSource.ts index edb336cf..5b0447a5 100644 --- a/src/model/MutableAreaDataSource.ts +++ b/src/model/MutableAreaDataSource.ts @@ -17,10 +17,14 @@ import CountriesLngLat from '../data/countries-with-lnglat.json' assert { type: import { logger } from '../logger.js' import { GradeContexts } from '../GradeUtils.js' import { sanitizeStrict } from '../utils/sanitize.js' +import { ExperimentalAuthorType } from '../db/UserTypes.js' +import { createInstance as createExperimentalUserDataSource } from '../model/ExperimentalUserDataSource.js' isoCountries.registerLocale(enJson) export default class MutableAreaDataSource extends AreaDataSource { + experimentalUserDataSource = createExperimentalUserDataSource() + async setDestinationFlag (user: MUUID, uuid: MUUID, flag: boolean): Promise { const session = await this.areaModel.startSession() let ret: AreaType | null = null @@ -101,7 +105,7 @@ export default class MutableAreaDataSource extends AreaDataSource { * @param parentUuid * @param countryCode */ - async addArea (user: MUUID, areaName: string, parentUuid: MUUID | null, countryCode?: string): Promise { + async addArea (user: MUUID, areaName: string, parentUuid: MUUID | null, countryCode?: string, experimentalAuthor?: ExperimentalAuthorType): Promise { if (parentUuid == null && countryCode == null) { throw new Error('Adding area failed. Must provide parent Id or country code') } @@ -121,14 +125,14 @@ export default class MutableAreaDataSource extends AreaDataSource { // see https://jira.mongodb.org/browse/NODE-2014 await session.withTransaction( async (session) => { - ret = await this._addArea(session, user, areaName, uuid) + ret = await this._addArea(session, user, areaName, uuid, experimentalAuthor) return ret }) // @ts-expect-error return ret } - async _addArea (session, user: MUUID, areaName: string, parentUuid: MUUID): Promise { + async _addArea (session, user: MUUID, areaName: string, parentUuid: MUUID, experimentalAuthor?: ExperimentalAuthorType): Promise { const parentFilter = { 'metadata.area_id': parentUuid } const parent = await this.areaModel.findOne(parentFilter).session(session).orFail(new UserInputError('Expecting 1 parent, found none.')) @@ -141,9 +145,15 @@ export default class MutableAreaDataSource extends AreaDataSource { parent.metadata.isBoulder = false } + // See https://github.com/OpenBeta/openbeta-graphql/issues/244 + let experimentaAuthorId: MUUID | null = null + if (experimentalAuthor != null) { + experimentaAuthorId = await this.experimentalUserDataSource.updateUser(session, experimentalAuthor.displayName, experimentalAuthor.url) + } + const change = await changelogDataSource.create(session, user, OperationType.addArea) const newChangeMeta: ChangeRecordMetadataType = { - user, + user: experimentaAuthorId ?? user, historyId: change._id, operation: OperationType.addArea, seq: 0 @@ -159,7 +169,7 @@ export default class MutableAreaDataSource extends AreaDataSource { const parentGradeContext = parent.gradeContext const newArea = newAreaHelper(areaName, parentAncestors, parentPathTokens, parentGradeContext) newArea.metadata.lnglat = parent.metadata.lnglat - newArea.createdBy = user + newArea.createdBy = experimentaAuthorId ?? user newArea._change = produce(newChangeMeta, draft => { draft.seq = 1 }) @@ -167,7 +177,7 @@ export default class MutableAreaDataSource extends AreaDataSource { // Make sure parent knows about this new area parent.children.push(newArea._id) - parent.updatedBy = user + parent.updatedBy = experimentaAuthorId ?? user await parent.save({ timestamps: false }) return rs1[0].toObject() } @@ -281,7 +291,7 @@ export default class MutableAreaDataSource extends AreaDataSource { throw new Error('Area update error. Reason: Area not found.') } - const { areaName, description, shortCode, isDestination, isLeaf, isBoulder, lat, lng } = document + const { areaName, description, shortCode, isDestination, isLeaf, isBoulder, lat, lng, experimentalAuthor } = document if (area.pathTokens.length === 1) { if (areaName != null || shortCode != null) throw new Error('Area update error. Reason: Updating country name or short code is not allowed.') @@ -313,18 +323,24 @@ export default class MutableAreaDataSource extends AreaDataSource { }) } + // See https://github.com/OpenBeta/openbeta-graphql/issues/244 + let experimentaAuthorId: MUUID | null = null + if (experimentalAuthor != null) { + experimentaAuthorId = await this.experimentalUserDataSource.updateUser(session, experimentalAuthor.displayName, experimentalAuthor.url) + } + const opType = OperationType.updateArea const change = await changelogDataSource.create(session, user, opType) const _change: ChangeRecordMetadataType = { - user, + user: experimentaAuthorId ?? user, historyId: change._id, prevHistoryId: area._change?.historyId._id, operation: opType, seq: 0 } area.set({ _change }) - area.updatedBy = user + area.updatedBy = experimentaAuthorId ?? user const cursor = await area.save() return cursor.toObject() } diff --git a/src/utils/sanitize.ts b/src/utils/sanitize.ts index cdb8019b..1cb96ef4 100644 --- a/src/utils/sanitize.ts +++ b/src/utils/sanitize.ts @@ -21,4 +21,4 @@ export const sanitizeStrict = (text: string): string => sanitizeHtml(text, { allowedTags: [], allowedAttributes: { } -}) +}).trim()