From 110b0c24a6c4b4a618b533a0284b72e7edcafa59 Mon Sep 17 00:00:00 2001 From: Viet Nguyen <3805254+vnugent@users.noreply.github.com> Date: Wed, 12 Jul 2023 19:56:29 +0200 Subject: [PATCH] refactor: simplify add media api to accept fewerparams for embedded tag (#339) - refactor: seed-db script should only download db file once - refactor: simplify embedded tag params when adding new media objects --- seed-db.sh | 15 ++++++--- src/db/MediaObjectTypes.ts | 9 ++++-- src/graphql/schema/Media.gql | 14 ++++---- src/model/MutableMediaDataSource.ts | 45 +++++++++++++++++++------- src/model/__tests__/MediaDataSource.ts | 2 +- 5 files changed, 59 insertions(+), 26 deletions(-) diff --git a/seed-db.sh b/seed-db.sh index 2b49b9b3..67bd0c66 100755 --- a/seed-db.sh +++ b/seed-db.sh @@ -1,13 +1,20 @@ -# This script rebuilds your local database with -# a copy of OpenBeta staging database. +# Rebuild your local database with a copy of OpenBeta staging database. +# +# To keep running time short, the script only downloads the remote +# database dump file once. Specify 'download' argument to force download. +# +# Syntax: +# ./seed-db.sh [download] # #!/bin/bash FILE_NAME="openbeta-stg-db.tar.gz" REMOTE_FILE="https://storage.googleapis.com/openbeta-dev-dbs/$FILE_NAME" -echo "Downloading db file(s)..." -wget --content-disposition $REMOTE_FILE +if [[ ! -f ${FILE_NAME} || ${1} == "download" ]]; then + echo "Downloading db file(s)..." + wget --content-disposition $REMOTE_FILE +fi rm -rf ./db-dumps/staging/openbeta diff --git a/src/db/MediaObjectTypes.ts b/src/db/MediaObjectTypes.ts index 933c8563..9a56417e 100644 --- a/src/db/MediaObjectTypes.ts +++ b/src/db/MediaObjectTypes.ts @@ -13,7 +13,7 @@ export interface MediaObject { format: ImageFormatType createdAt: Date size: number - entityTags: EntityTag[] + entityTags?: EntityTag[] } export interface EntityTag { @@ -52,6 +52,11 @@ export interface TagsLeaderboardType { allTime: AllTimeTagStats } +/** + * For creating a new Media object doc + */ +export type NewMediaObjectDoc = Omit + export interface UserMediaGQLQueryInput { userUuid: string maxFiles?: number @@ -84,7 +89,7 @@ export interface EntityTagDeleteInput { */ export type MediaObjectGQLInput = Pick & { userUuid: string - entityTags?: Array> + entityTag?: Omit } /** diff --git a/src/graphql/schema/Media.gql b/src/graphql/schema/Media.gql index 04645a37..fc4dd58c 100644 --- a/src/graphql/schema/Media.gql +++ b/src/graphql/schema/Media.gql @@ -1,29 +1,29 @@ type Mutation { """ - Add one or more media objects + Add one or more media objects. Each media object may contain one tag. """ addMediaObjects(input: [NewMediaObjectInput]): [MediaWithTags] """ - Add sone media object + Delete one media object. """ deleteMediaObject(input: MediaDeleteInput!): Boolean! """ - Add an entity tag to a media + Add an entity tag to a media. """ addEntityTag(input: MediaEntityTagInput): EntityTag! """ - Remove an entity tag from a media + Remove an entity tag from a media. """ removeEntityTag(input: EntityTagDeleteInput!): Boolean! } type Query { """ - Get single media object + Get single media object. """ media(input: MediaInput): MediaWithTags @@ -46,7 +46,7 @@ type Query { getUserMediaPagination(input: UserMediaInput): UserMedia """ - Get a list of users and their tagged photo count + Get a list of users and their tagged photo count. """ getTagsLeaderboard(limit: Int): TagsLeaderboard } @@ -172,7 +172,7 @@ input NewMediaObjectInput { format: String! size: Int! mediaUrl: String! - entityTags: [EmbeddedEntityInput] + entityTag: EmbeddedEntityInput } input EmbeddedEntityInput { diff --git a/src/model/MutableMediaDataSource.ts b/src/model/MutableMediaDataSource.ts index 3d7f20ef..75cdef29 100644 --- a/src/model/MutableMediaDataSource.ts +++ b/src/model/MutableMediaDataSource.ts @@ -3,17 +3,13 @@ import mongoose from 'mongoose' import muuid from 'uuid-mongodb' import MediaDataSource from './MediaDataSource.js' -import { EntityTag, EntityTagDeleteInput, MediaObject, MediaObjectGQLInput, AddTagEntityInput } from '../db/MediaObjectTypes.js' +import { EntityTag, EntityTagDeleteInput, MediaObject, MediaObjectGQLInput, AddTagEntityInput, NewMediaObjectDoc } from '../db/MediaObjectTypes.js' import MutableAreaDataSource from './MutableAreaDataSource.js' export default class MutableMediaDataSource extends MediaDataSource { areaDS = MutableAreaDataSource.getInstance() - /** - * Add a new entity tag (a climb or area) to a media object. - * @returns new EntityTag . 'null' if the entity already exists. - */ - async addEntityTag ({ mediaId, entityUuid, entityType }: AddTagEntityInput): Promise { + async getEntityDoc ({ entityUuid, entityType }: Omit): Promise { let newEntityTagDoc: EntityTag switch (entityType) { case 0: { @@ -61,6 +57,16 @@ export default class MutableMediaDataSource extends MediaDataSource { default: throw new UserInputError(`Entity type ${entityType} not supported.`) } + return newEntityTagDoc + } + + /** + * Add a new entity tag (a climb or area) to a media object. + * @returns new EntityTag . 'null' if the entity already exists. + */ + async addEntityTag ({ mediaId, entityUuid, entityType }: AddTagEntityInput): Promise { + // Find the entity we want to tag + const newEntityTagDoc = await this.getEntityDoc({ entityUuid, entityType }) // We treat 'entityTags' like a Set - can't tag the same climb/area id twice. // See https://stackoverflow.com/questions/33576223/using-mongoose-mongodb-addtoset-functionality-on-array-of-objects @@ -106,16 +112,31 @@ export default class MutableMediaDataSource extends MediaDataSource { } /** - * Add one or more media objects. + * Add one or more media objects. The embedded entityTag may have one tag. */ async addMediaObjects (input: MediaObjectGQLInput[]): Promise { - const docs = input.map(entry => ({ - ...entry, - userUuid: muuid.from(entry.userUuid) + const docs: NewMediaObjectDoc[] = await Promise.all(input.map(async entry => { + const { userUuid: userUuidStr, mediaUrl, width, height, format, size, entityTag } = entry + let newTag: EntityTag | undefined + if (entityTag != null) { + newTag = await this.getEntityDoc({ + entityType: entityTag.entityType, + entityUuid: muuid.from(entityTag.entityId) + }) + } + + return ({ + mediaUrl, + width, + height, + format, + size, + userUuid: muuid.from(userUuidStr), + ...newTag != null && { entityTags: [newTag] } + }) })) const rs = await this.mediaObjectModel.insertMany(docs, { lean: true }) - // @ts-expect-error return rs != null ? rs : [] } @@ -126,7 +147,7 @@ export default class MutableMediaDataSource extends MediaDataSource { const filter = { _id: mediaId } const rs = await this.mediaObjectModel.find(filter).orFail(new UserInputError(`Media Id not found ${mediaId.toString()}`)) - if (rs[0].entityTags.length > 0) { + if ((rs[0].entityTags?.length ?? 0) > 0) { throw new UserInputError('Cannot delete media object with non-empty tags. Delete tags first.') } diff --git a/src/model/__tests__/MediaDataSource.ts b/src/model/__tests__/MediaDataSource.ts index 273aaa62..83a65c7c 100644 --- a/src/model/__tests__/MediaDataSource.ts +++ b/src/model/__tests__/MediaDataSource.ts @@ -194,7 +194,7 @@ describe('MediaDataSource', () => { const rs = await media.addMediaObjects([{ ...TEST_MEDIA, mediaUrl: 'photo101.jpg', - entityTags: [{ entityType: 0, entityId: muuid.v4().toUUID().toString() }] + entityTag: { entityType: 0, entityId: climbIdForTagging.toUUID().toString() } } ])