From db8c2467f98adb4b64b36f3b14f7fb32133dc103 Mon Sep 17 00:00:00 2001 From: viet nguyen Date: Sun, 30 Apr 2023 22:35:23 +0200 Subject: [PATCH] chore: improve get media for area query chore: climb/area/user profile media share the same type --- src/db/MediaTypes.ts | 2 +- src/graphql/media/queries.ts | 4 +- src/graphql/resolvers.ts | 4 +- src/model/AreaDataSource.ts | 198 +---------------------------------- src/model/MediaDataSource.ts | 180 +++++++++++++++++++++++++++++-- 5 files changed, 178 insertions(+), 210 deletions(-) diff --git a/src/db/MediaTypes.ts b/src/db/MediaTypes.ts index 7fc562b4..422e6f38 100644 --- a/src/db/MediaTypes.ts +++ b/src/db/MediaTypes.ts @@ -48,7 +48,7 @@ export interface SimpleTag { name: string type: number } -export interface UserMediaWithTags extends MediaObjectType { +export interface MediaWithTags extends MediaObjectType { climbTags: SimpleTag[] areaTags: SimpleTag[] } diff --git a/src/graphql/media/queries.ts b/src/graphql/media/queries.ts index 07664931..4dbd67c5 100644 --- a/src/graphql/media/queries.ts +++ b/src/graphql/media/queries.ts @@ -1,4 +1,4 @@ -import { MediaListByAuthorType, UserMediaWithTags, TagsLeaderboardType } from '../../db/MediaTypes.js' +import { MediaListByAuthorType, MediaWithTags, TagsLeaderboardType } from '../../db/MediaTypes.js' import { DataSourcesType } from '../../types.js' const MediaQueries = { @@ -22,7 +22,7 @@ const MediaQueries = { /** * Return most recent tags */ - getUserMedia: async (_, { userUuid, limit = 1000 }: { limit: number | undefined, userUuid: string }, { dataSources }): Promise => { + getUserMedia: async (_, { userUuid, limit = 1000 }: { limit: number | undefined, userUuid: string }, { dataSources }): Promise => { const { media }: DataSourcesType = dataSources return await media.getUserMedia(userUuid, limit) }, diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts index 1d8953e3..e1298bed 100644 --- a/src/graphql/resolvers.ts +++ b/src/graphql/resolvers.ts @@ -234,8 +234,8 @@ const resolvers = { }), media: async (node: any, args: any, { dataSources }) => { - const { areas }: { areas: AreaDataSource } = dataSources - return await areas.findMediaByAreaId(node.metadata.area_id, node.ancestors) + const { media }: { media: MediaDataSource } = dataSources + return await media.findMediaByAreaId(node.metadata.area_id, node.ancestors) }, createdBy: (node: AreaType) => node?.createdBy?.toUUID().toString(), diff --git a/src/model/AreaDataSource.ts b/src/model/AreaDataSource.ts index aeee16b5..1e9e8c00 100644 --- a/src/model/AreaDataSource.ts +++ b/src/model/AreaDataSource.ts @@ -1,6 +1,6 @@ import { MongoDataSource } from 'apollo-datasource-mongodb' import { Filter } from 'mongodb' -import muuid, { MUUID } from 'uuid-mongodb' +import muuid from 'uuid-mongodb' import bboxPolygon from '@turf/bbox-polygon' import { getAreaModel, getMediaModel, getMediaObjectModel } from '../db/index.js' @@ -9,8 +9,6 @@ import { GQLFilter, AreaFilterParams, PathTokenParams, LeafStatusParams, Compari import { getClimbModel } from '../db/ClimbSchema.js' import { ClimbGQLQueryType } from '../db/ClimbTypes.js' import { logger } from '../logger.js' -import { UserMediaWithTags } from '../db/MediaTypes.js' -import { joiningTagWithMediaObject } from './MediaDataSource.js' export default class AreaDataSource extends MongoDataSource { areaModel = getAreaModel() @@ -124,200 +122,6 @@ export default class AreaDataSource extends MongoDataSource { return rs } - /** - * Find all climb tags and area tags for a given areaId - * @param areaId area ID to search - * @param ancestors this area ancestors - * @returns array of base tag - */ - async findMediaByAreaId (areaId: MUUID, ancestors: string): Promise { - /** - * Find all area tags whose ancestors and children have 'areaId' - */ - const taggedAreas = await getMediaModel().aggregate([ - ...joiningTagWithMediaObject, - { - $unset: ['_id', 'onModel', 'mediaType', 'destType', 'mediaUuid'] - }, - { - // SELECT * - // FROM media - // LEFT JOIN areas - // ON media.destinationId == areas.metadata.area_id - $lookup: { - from: 'areas', // other collection name - foreignField: 'metadata.area_id', - localField: 'destinationId', - as: 'taggedArea', - pipeline: [{ - $match: { - $expr: { - $or: [ - // { // Case 1: given a child area, inheret its ancestor's photos - // // - input: A,B,C <-- area I want to search for tags - // // - regex: A,B - // $regexMatch: { - // input: ancestors, - // regex: '$ancestors', - // options: 'i' - // } - // }, - { // Case 2: given a ancestor area, inherit descendant photos - // - input: A,B,C - // - regex: A,B <-- area I want to search for tags - $regexMatch: { - input: '$ancestors', - regex: ancestors, - options: 'i' - } - } - ] - } - } - }] - } - }, - { - $match: { - taggedArea: { - $ne: [] - } - } - }, - { - $unset: ['destinationId'] - }, - { - $set: { - taggedArea: { - $map: { - input: '$taggedArea', - in: { - id: '$$this.metadata.area_id', - name: '$$this.area_name', - type: 1 - } - } - } - } - }, - { - $unwind: '$taggedArea' - }, - { - $group: { - _id: { - mediaUrl: '$mediaUrl', - width: '$width', - height: '$height', - birthTime: '$birthTime', - mtime: '$mtime', - format: '$format', - size: '$size' - }, - taggedArea: { $push: '$taggedArea' } - } - }, - { - $addFields: { - '_id.areaTags': '$taggedArea' - } - }, - { $replaceRoot: { newRoot: '$_id' } } - ]) - - /** - * Find all climb tags whose ancestors have areaId - */ - const taggeClimbs = await getMediaModel() - .aggregate([ - ...joiningTagWithMediaObject, - { - $unset: ['_id', 'onModel', 'mediaType', 'destType', 'mediaUuid'] - }, - { - // SELECT * - // FROM media - // LEFT OUTER climbs - // ON climbs._id == media.destinationId - $lookup: { - from: 'climbs', // other collection name - foreignField: '_id', // climb._id - localField: 'destinationId', - as: 'taggedClimbs', - pipeline: [{ - $lookup: { // also allow ancestor areas to inherent climb photo - from: 'areas', // other collection name - foreignField: 'metadata.area_id', - localField: 'metadata.areaRef', // climb.metadata.areaRef - as: 'area' - } - }, - { - $match: { - 'area.ancestors': { $regex: areaId.toUUID().toString() } - } - } - ] - } - }, - { - $match: { - taggedClimbs: { - $ne: [] - } - } - }, - { - $unset: ['destinationId'] - }, - { - $set: { - taggedClimbs: { - $map: { - input: '$taggedClimbs', - in: { - id: '$$this._id', - name: '$$this.name', - type: 0 - } - } - } - } - }, - { - $unwind: '$taggedClimbs' - }, - { - $group: { - _id: { - mediaUrl: '$mediaUrl', - width: '$width', - height: '$height', - birthTime: '$birthTime', - mtime: '$mtime', - format: '$format', - size: '$size' - }, - climbTags: { $push: '$taggedClimbs' } - } - }, - { - $addFields: { - '_id.climbTags': '$climbTags' - } - }, - { $replaceRoot: { newRoot: '$_id' } } - ]) - - // combine 2 result sets - if (taggeClimbs != null) { - return taggeClimbs.concat(taggedAreas) - } else { - return taggedAreas - } - } - /** * Find a climb by uuid. Also return the parent area object (crag or boulder). * diff --git a/src/model/MediaDataSource.ts b/src/model/MediaDataSource.ts index 8efbcc26..362afaec 100644 --- a/src/model/MediaDataSource.ts +++ b/src/model/MediaDataSource.ts @@ -2,11 +2,11 @@ import { MongoDataSource } from 'apollo-datasource-mongodb' import muid, { MUUID } from 'uuid-mongodb' import { getMediaModel, getMediaObjectModel } from '../db/index.js' -import { MediaType, MediaListByAuthorType, TagsLeaderboardType, UserMediaWithTags } from '../db/MediaTypes.js' +import { MediaType, MediaListByAuthorType, TagsLeaderboardType, MediaWithTags } from '../db/MediaTypes.js' export default class MediaDataSource extends MongoDataSource { tagModel = getMediaModel() - mediaMediaObjectModel = getMediaObjectModel() + mediaObjectModel = getMediaObjectModel() async getTagsByMediaIds (uuidList: string[]): Promise { if (uuidList !== undefined && uuidList.length > 0) { @@ -37,7 +37,7 @@ export default class MediaDataSource extends MongoDataSource { { $lookup: { localField: 'mediaUrl', - from: this.mediaMediaObjectModel.modelName, // Foreign collection name + from: this.mediaObjectModel.modelName, // Foreign collection name foreignField: 'name', as: 'meta' // add a new parent field } @@ -86,8 +86,8 @@ export default class MediaDataSource extends MongoDataSource { * Get all photos for a user * @param userLimit */ - async getUserMedia (uuidStr: string, userLimit: number = 10): Promise { - const rs = await getMediaObjectModel().aggregate([ + async getUserMedia (uuidStr: string, userLimit: number = 10): Promise { + const rs = await getMediaObjectModel().aggregate([ { $match: { $expr: { @@ -217,11 +217,11 @@ export default class MediaDataSource extends MongoDataSource { * where media.mediaUrl == media_objects.name and media.estinationId == * ``` * @param climbId - * @returns Tag object + * @returns `MediaWithTags` array */ - async findMediaByClimbId (climbId: MUUID, climbName: string): Promise { + async findMediaByClimbId (climbId: MUUID, climbName: string): Promise { const rs = await this.tagModel - .aggregate([ + .aggregate([ { $match: { destinationId: climbId } }, ...joiningTagWithMediaObject, { @@ -238,6 +238,170 @@ export default class MediaDataSource extends MongoDataSource { areaTags: [] })) } + + /** + * Find all media tags associated with a given area. An area can have its own + * tags or inherit tags from their children. A note on inheritance: + * + * 1. A parent area and their ancestors will inherit tags from their children. + * 2. Child area or climb will **not** inherit their parent/ancestor tags. + * + * @param areaId + * @param ancestors + * @returns `UserMediaWithTags` array + */ + async findMediaByAreaId (areaId: MUUID, ancestors: string): Promise { + const taggedClimbsPipeline = [ + { + // SELECT * + // FROM media + // LEFT OUTER climbs + // ON climbs._id == media.destinationId + $lookup: { + from: 'climbs', // other collection name + foreignField: '_id', // climb._id + localField: 'destinationId', + as: 'taggedClimb', + pipeline: [{ + $lookup: { // also allow ancestor areas to inherent climb photo + from: 'areas', // other collection name + foreignField: 'metadata.area_id', + localField: 'metadata.areaRef', // climb.metadata.areaRef + as: 'area' + } + }, + { + $match: { + 'area.ancestors': { $regex: areaId.toUUID().toString() } + } + } + ] + } + }, + { + $set: { + taggedClimb: { + $map: { + input: '$taggedClimb', + in: { + id: '$$this._id', + name: '$$this.name', + type: 0 + } + } + } + } + }, + { + $unwind: { + path: '$taggedClimb', + preserveNullAndEmptyArrays: true + } + } + ] + + const taggedAreasPipeline = [{ + // SELECT * + // FROM media + // LEFT JOIN areas + // ON media.destinationId == areas.metadata.area_id + $lookup: { + from: 'areas', // other collection name + foreignField: 'metadata.area_id', + localField: 'destinationId', + as: 'taggedArea', + pipeline: [ + { + $match: { + $expr: { + // Given an ancestor area, match descendant tags + // - input: A,B,C <-- Area C has a tag + // - regex: A,B <-- My search area is B. Yes, there's a match. + $regexMatch: { + input: '$ancestors', + regex: ancestors, + options: 'i' + } + } + } + }] + } + }, + { + $set: { + taggedArea: { + $map: { + input: '$taggedArea', + in: { + id: '$$this.metadata.area_id', + name: '$$this.area_name', + type: 1 + } + } + } + } + }, + { + $unwind: { + path: '$taggedArea', + preserveNullAndEmptyArrays: true + } + } + ] + + /** + * Find all area tags whose ancestors and children have 'areaId' + */ + return await getMediaModel().aggregate([ + ...joiningTagWithMediaObject, + { + $unset: ['_id', 'onModel', 'mediaType', 'destType', 'mediaUuid'] + }, + ...taggedClimbsPipeline, + ...taggedAreasPipeline, + { + $unset: ['destinationId'] + }, + { + $match: { + $or: [ + { + taggedClimb: { + $exists: true + } + }, + { + taggedArea: { + $exists: true + } + } + ] + } + }, + { + $group: { + _id: { + mediaUrl: '$mediaUrl', + width: '$width', + height: '$height', + birthTime: '$birthTime', + mtime: '$mtime', + format: '$format', + size: '$size' + }, + taggedAreas: { $push: '$taggedArea' }, + taggedClimbs: { $push: '$taggedClimb' } + } + }, + { + $addFields: { + '_id.areaTags': '$taggedAreas', + '_id.climbTags': '$taggedClimbs' + } + }, + { $replaceRoot: { newRoot: '$_id' } } + ]) + } } /**