From 300a28bb97ef76a31e60e833463201fc022b57ad Mon Sep 17 00:00:00 2001 From: Viet Nguyen <3805254+vnugent@users.noreply.github.com> Date: Mon, 13 Mar 2023 15:37:30 +0100 Subject: [PATCH] feat: experimental author info (#245) --- src/db/ClimbTypes.ts | 4 ++ src/db/UserSchema.ts | 23 +++++++++++ src/db/UserTypes.ts | 10 +++++ src/db/index.ts | 12 +++++- src/graphql/schema/ClimbEdit.gql | 6 +++ src/model/ExperimentalUserDataSource.ts | 53 +++++++++++++++++++++++++ src/model/MutableClimbDataSource.ts | 16 +++++++- 7 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 src/db/UserSchema.ts create mode 100644 src/db/UserTypes.ts create mode 100644 src/model/ExperimentalUserDataSource.ts diff --git a/src/db/ClimbTypes.ts b/src/db/ClimbTypes.ts index 9de94172..d06c0ce4 100644 --- a/src/db/ClimbTypes.ts +++ b/src/db/ClimbTypes.ts @@ -154,6 +154,10 @@ export interface ClimbChangeInputType { protection?: string fa?: string length?: number + experimentalAuthor?: { + displayName: string + url: string + } } type UpdatableClimbFieldsType = Pick diff --git a/src/db/UserSchema.ts b/src/db/UserSchema.ts new file mode 100644 index 00000000..c36641bc --- /dev/null +++ b/src/db/UserSchema.ts @@ -0,0 +1,23 @@ +import mongoose from 'mongoose' +import muuid from 'uuid-mongodb' + +import { ExperimentalUserType } from './UserTypes.js' + +const { Schema } = mongoose + +export const ExperimentalUserSchema = new Schema({ + _id: { + type: 'object', + value: { type: 'Buffer' }, + default: () => muuid.v4() + }, + displayName: { type: Schema.Types.String, required: true, index: true }, + url: { type: Schema.Types.String, required: true, index: true } +}, { + _id: false, + timestamps: true +}) + +export const getExperimentalUserModel = (): mongoose.Model => { + return mongoose.model('exp_users', ExperimentalUserSchema) +} diff --git a/src/db/UserTypes.ts b/src/db/UserTypes.ts new file mode 100644 index 00000000..15a6645c --- /dev/null +++ b/src/db/UserTypes.ts @@ -0,0 +1,10 @@ +import { MUUID } from 'uuid-mongodb' + +export interface ExperimentalUserType { + _id: MUUID + displayName: string + nickname: string + url: string + createdAt: Date + updatedAt: Date +} diff --git a/src/db/index.ts b/src/db/index.ts index b50ed1af..dbe6a957 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -9,6 +9,7 @@ import { getTickModel } from './TickSchema.js' import { getXMediaModel } from './XMediaSchema.js' import { getPostModel } from './PostSchema.js' import { getChangeLogModel } from './ChangeLogSchema.js' +import { getExperimentalUserModel } from './UserSchema.js' import { logger } from '../logger.js' import streamListener from './edit/streamListener.js' @@ -85,4 +86,13 @@ export const defaultPostConnect = async (): Promise => { // eslint-disable-next-line process.on('SIGINT', gracefulExit).on('SIGTERM', gracefulExit) -export { getMediaModel, getAreaModel, getTickModel, getClimbModel, getChangeLogModel, getXMediaModel, getPostModel } +export { + getMediaModel, + getAreaModel, + getTickModel, + getClimbModel, + getChangeLogModel, + getXMediaModel, + getPostModel, + getExperimentalUserModel +} diff --git a/src/graphql/schema/ClimbEdit.gql b/src/graphql/schema/ClimbEdit.gql index 29cf9435..a82284b6 100644 --- a/src/graphql/schema/ClimbEdit.gql +++ b/src/graphql/schema/ClimbEdit.gql @@ -42,6 +42,7 @@ input SingleClimbChangeInput { fa: String "Length in meters" length: Int + experimentalAuthor: ExperimentalAuthorType } input DisciplineType { @@ -63,3 +64,8 @@ input DisciplineType { "https://en.wikipedia.org/wiki/Top_rope_climbing" tr: Boolean } + +input ExperimentalAuthorType { + displayName: String! + url: String! +} diff --git a/src/model/ExperimentalUserDataSource.ts b/src/model/ExperimentalUserDataSource.ts new file mode 100644 index 00000000..b093bb7b --- /dev/null +++ b/src/model/ExperimentalUserDataSource.ts @@ -0,0 +1,53 @@ +import { MongoDataSource } from 'apollo-datasource-mongodb' +import mongoose, { ClientSession } from 'mongoose' +import muuid, { MUUID } from 'uuid-mongodb' +import { v5 as uuidv5, NIL } from 'uuid' + +import { getExperimentalUserModel } from '../db/index.js' +import { ExperimentalUserType } from '../db/UserTypes.js' + +export default class MediaDataSource extends MongoDataSource { + experimentUserModel = getExperimentalUserModel() + + /** + * Create or update a user. + * @param session transaction + * @param inputDisplayName + * @param inputUrl + * @returns User UUID if successful. null otherwise. + */ + async updateUser (session: ClientSession, inputDisplayName: string, inputUrl: string): Promise { + const url: string = inputUrl + let displayName = inputDisplayName != null ? inputDisplayName.trim().substring(0, 50) : '' + let uuid: MUUID + if (url == null || url.trim() === '') { + if (displayName === '') { + // displayName and url are both null/empty + return null + } + uuid = muuid.v4() + } else { + // generate uuid from inputUrl + uuid = muuid.from(uuidv5(inputUrl, NIL)) + if (displayName === '') { + displayName = `u_${uuid.toUUID().toString()}` + } + } + + const filter = { + _id: uuid + } + const doc = { + displayName, + url + } + const rs = await this.experimentUserModel.findOneAndUpdate(filter, doc, { new: true, upsert: true, session }).lean() + + if (rs._id != null) { + return rs._id + } + return null + } +} + +export const createInstance = (): MediaDataSource => new MediaDataSource(mongoose.connection.db.collection(getExperimentalUserModel().modelName)) diff --git a/src/model/MutableClimbDataSource.ts b/src/model/MutableClimbDataSource.ts index 82829b28..1ce0015a 100644 --- a/src/model/MutableClimbDataSource.ts +++ b/src/model/MutableClimbDataSource.ts @@ -4,6 +4,7 @@ import { ClientSession } from 'mongoose' import { ClimbChangeDocType, ClimbChangeInputType, ClimbEditOperationType } from '../db/ClimbTypes.js' import ClimbDataSource from './ClimbDataSource.js' +import { createInstance as createExperimentalUserDataSource } from './ExperimentalUserDataSource.js' import { sanitizeDisciplines, gradeContextToGradeScales, createGradeObject } from '../GradeUtils.js' import { getClimbModel } from '../db/ClimbSchema.js' import { ChangeRecordMetadataType } from '../db/ChangeLogType.js' @@ -11,6 +12,8 @@ import { changelogDataSource } from './ChangeLogDataSource.js' import { sanitize, sanitizeStrict } from '../utils/sanitize.js' export default class MutableClimbDataSource extends ClimbDataSource { + experimentalUserDataSource = createExperimentalUserDataSource() + async _addOrUpdateClimbs (userId: MUUID, session: ClientSession, parentId: MUUID, userInput: ClimbChangeInputType[]): Promise { const newClimbIds = new Array(userInput.length) for (let i = 0; i < newClimbIds.length; i++) { @@ -107,6 +110,15 @@ export default class MutableClimbDataSource extends ClimbDataSource { throw new UserInputError(`Can't add new climbs without name. (Index[index=${i}])`) } + // See https://github.com/OpenBeta/openbeta-graphql/issues/244 + const author = userInput[i].experimentalAuthor + let experimentalUserId: MUUID | null = null + if (author != null) { + experimentalUserId = await this.experimentalUserDataSource.updateUser(session, author.displayName, author.url) + } + + console.log('#xperimental uiser', experimentalUserId) + const typeSafeDisciplines = sanitizeDisciplines(userInput[i]?.disciplines) const grade = userInput[i].grade @@ -139,10 +151,10 @@ export default class MutableClimbDataSource extends ClimbDataSource { lnglat: parent.metadata.lnglat, ...userInput[i]?.leftRightIndex != null && { left_right_index: userInput[i].leftRightIndex } }, - ...!idList[i].existed && { createdBy: userId }, + ...!idList[i].existed && { createdBy: experimentalUserId ?? userId }, ...idList[i].existed && { updatedBy: userId }, _change: { - user: userId, + user: experimentalUserId ?? userId, historyId: change._id, prevHistoryId: undefined, operation: opType,