Skip to content

Commit

Permalink
chore: add 'getUserMedia' query to backend to simplify the frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
viet nguyen committed Apr 28, 2023
1 parent d3f1fb2 commit 1619caf
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 57 deletions.
29 changes: 29 additions & 0 deletions src/cdn/cdnClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Storage } from '@google-cloud/storage'

const storage = new Storage({
projectId: 'openbeta',
credentials: {
type: 'service-account',
client_email: '[email protected]',
private_key: ''
}
})

/**
* Get a user's media files. May not need this.
* @param uuidStr user id in uuid v4 format
* @returns a list media objects including associated tags
*/
export const getUserMedia = async (uuidStr: string): Promise<string[]> => {
const [files] = await storage.bucket('openbeta-staging').getFiles({
autoPaginate: false,
prefix: `u/${uuidStr}/`, //
delimiter: '/'
})
return files.reduce<string[]>((acc, curr) => {
if (!curr.name.endsWith('.json')) {
acc.push(`/${curr.name}`)
}
return acc
}, [])
}
9 changes: 9 additions & 0 deletions src/db/MediaTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ export interface MediaListByAuthorType {
tagList: TagType[]
}

export interface SimpleTag {
id: MUUID
name: string
}
export interface UserMediaWithTags extends MediaMetaType {
climbTags: SimpleTag[]
areaTags: SimpleTag[]
}

export interface MediaInputType {
mediaUuid: MUUID
mediaUrl: string
Expand Down
16 changes: 5 additions & 11 deletions src/graphql/media/MediaResolvers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CompleteAreaTag, CompleteClimbTag, MediaListByAuthorType, RefModelType, TagEntryResultType, TagType, BaseTagType } from '../../db/MediaTypes.js'
import { CompleteAreaTag, CompleteClimbTag, MediaListByAuthorType, RefModelType, TagEntryResultType, TagType, BaseTagType, SimpleTag } from '../../db/MediaTypes.js'
import AreaDataSource from '../../model/AreaDataSource.js'
import { getUserNickFromMediaDir } from '../../utils/helpers.js'

Expand Down Expand Up @@ -63,20 +63,14 @@ const MediaResolvers = {
}
},

SimpleTag: {
id: async (node: SimpleTag) => node.id.toUUID().toString()
},

MediaListByAuthorType: {
authorUuid: (node: MediaListByAuthorType) => node._id
},

// ClimbTag: {
// id: (node: ClimbTagType) => node._id.toString(),
// mediaUuid: (node: ClimbTagType) => node.mediaUuid.toUUID().toString()
// },

// AreaTag: {
// id: (node: AreaTagType) => node._id.toString(),
// mediaUuid: (node: AreaTagType) => node.mediaUuid.toUUID().toString()
// },

DeleteTagResult: {
// nothing to override
}
Expand Down
10 changes: 9 additions & 1 deletion src/graphql/media/queries.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MediaListByAuthorType, TagsLeaderboardType } from '../../db/MediaTypes.js'
import { MediaListByAuthorType, UserMediaWithTags, TagsLeaderboardType } from '../../db/MediaTypes.js'
import { DataSourcesType } from '../../types.js'

const MediaQueries = {
Expand All @@ -19,6 +19,14 @@ const MediaQueries = {
return await media.getRecentTags(userLimit)
},

/**
* Return most recent tags
*/
getUserMedia: async (_, { userUuid, limit = 1000 }: { limit: number | undefined, userUuid: string }, { dataSources }): Promise<UserMediaWithTags[]> => {
const { media }: DataSourcesType = dataSources
return await media.getUserPhotos(userUuid, limit)
},

getTagsLeaderboard: async (_, { limit = 30 }: { limit: number }, { dataSources }): Promise<TagsLeaderboardType[]> => {
const { media }: DataSourcesType = dataSources
return await media.getTagsLeaderboard(limit)
Expand Down
45 changes: 35 additions & 10 deletions src/graphql/schema/Media.gql
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ type Query {
getRecentTags(userLimit: Int): [MediaListByAuthorType]
}

type Query {
getUserMedia(userUuid: ID!): [MediaWithTags]
}

type Query {
"""
Get a list of users and their tagged photo count
Expand All @@ -26,7 +30,17 @@ type TagsLeaderboardType {
total: Int!
}

"Core attributes of a tag"
"Media metadata"
interface IMediaMetadata {
width: Int!
height: Int!
format: String!
mtime: Date!
birthTime: Date!
size: Int!
}

"Attributes of a tag"
interface ITag {
id: ID!
username: String
Expand All @@ -35,16 +49,10 @@ interface ITag {
mediaType: Int!
destination: ID!
destType: Int!
width: Int!
height: Int!
format: String!
mtime: Date!
birthTime: Date!
size: Int!
}

"An area tag"
type AreaTag implements ITag {
type AreaTag implements ITag & IMediaMetadata {
id: ID!
username: String
mediaUuid: ID!
Expand All @@ -62,7 +70,7 @@ type AreaTag implements ITag {
}

"A climb tag"
type ClimbTag implements ITag {
type ClimbTag implements ITag & IMediaMetadata{
id: ID!
username: String
mediaUuid: ID!
Expand All @@ -82,7 +90,7 @@ type ClimbTag implements ITag {
union MediaTag = AreaTag | ClimbTag

"Representing a tag without the tagged climb/area. Used by climb & area queries."
type BaseTag implements ITag {
type BaseTag implements ITag & IMediaMetadata {
id: ID!
username: String
mediaUuid: ID!
Expand All @@ -104,6 +112,23 @@ type MediaListByAuthorType {
tagList: [MediaTag]
}

type SimpleTag {
id: ID!
name: String!
}

type MediaWithTags implements IMediaMetadata {
name: String!
width: Int!
height: Int!
format: String!
mtime: Date!
birthTime: Date!
size: Int!
climbTags: [SimpleTag]
areaTags: [SimpleTag]
}

union TagEntryResult = ClimbTag | AreaTag

"Result of a delete tag operation"
Expand Down
38 changes: 4 additions & 34 deletions src/model/AreaDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { getClimbModel } from '../db/ClimbSchema.js'
import { ClimbGQLQueryType } from '../db/ClimbTypes.js'
import { logger } from '../logger.js'
import { BaseTagType } from '../db/MediaTypes.js'
import { joiningTagWithMediaObject } from './MediaDataSource.js'

export default class AreaDataSource extends MongoDataSource<AreaType> {
areaModel = getAreaModel()
Expand Down Expand Up @@ -134,7 +135,7 @@ export default class AreaDataSource extends MongoDataSource<AreaType> {
* Find all area tags whose ancestors and children have 'areaId'
*/
const taggedAreas = await getMediaModel().aggregate([
...this.joiningTagWithMediaObject,
...joiningTagWithMediaObject,
{
// SELECT *
// FROM media
Expand Down Expand Up @@ -187,7 +188,7 @@ export default class AreaDataSource extends MongoDataSource<AreaType> {
*/
const taggeClimbs = await getMediaModel()
.aggregate([
...this.joiningTagWithMediaObject,
...joiningTagWithMediaObject,
{
// SELECT *
// FROM media
Expand Down Expand Up @@ -278,37 +279,6 @@ export default class AreaDataSource extends MongoDataSource<AreaType> {
return null
}

/**
* A reusable aggregation pipeline for 'joining' tag collection and media object collection.
*
* ```
* select *
* from media, media_objects
* where media.mediaUrl == media_objects.name
* ```
*/
joiningTagWithMediaObject = [
{
$lookup: {
localField: 'mediaUrl',
from: this.mediaObjectModal.modelName, // Foreign collection name
foreignField: 'name',
as: 'meta' // add a new parent field
}
},
{ $unwind: '$meta' },
{
$unset: ['meta.name', 'meta._id', 'meta.createdAt', 'meta.updatedAt']
},
{
$replaceWith: {
$mergeObjects: ['$$ROOT', '$meta']
}
},
{
$unset: ['meta']
}]

/**
* Find tags for a given climb id.
*
Expand All @@ -325,7 +295,7 @@ export default class AreaDataSource extends MongoDataSource<AreaType> {
const rs = await this.tagModel
.aggregate([
{ $match: { destinationId: climbId } },
...this.joiningTagWithMediaObject
...joiningTagWithMediaObject
])

return rs
Expand Down
121 changes: 120 additions & 1 deletion src/model/MediaDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { MongoDataSource } from 'apollo-datasource-mongodb'
import muid from 'uuid-mongodb'

import { getMediaModel, getMediaObjectModel } from '../db/index.js'
import { MediaType, MediaListByAuthorType, TagsLeaderboardType } from '../db/MediaTypes.js'
import { MediaType, MediaListByAuthorType, TagsLeaderboardType, UserMediaWithTags } from '../db/MediaTypes.js'

export default class MediaDataSource extends MongoDataSource<MediaType> {
tagModel = getMediaModel()
Expand Down Expand Up @@ -82,6 +82,92 @@ export default class MediaDataSource extends MongoDataSource<MediaType> {
return rs
}

/**
* Get all photos for a user
* @param userLimit
*/
async getUserPhotos (uuidStr: string, userLimit: number = 10): Promise<UserMediaWithTags[]> {
// const list = await getUserMedia(uuidStr)
// console.log('#list', list)

const rs = await getMediaObjectModel().aggregate<UserMediaWithTags>([
{
$match: {
$expr: {
$eq: [{ $substr: ['$name', 3, 36] }, uuidStr]
}
}
},
{
$lookup: {
localField: 'name',
from: 'media', // Foreign collection name
foreignField: 'mediaUrl',
as: 'climbTags', // add a new parent field
pipeline: [
{
$lookup: {
from: 'climbs', // other collection name
foreignField: '_id', // climb._id
localField: 'destinationId',
as: 'taggedClimbs'
}

},
{
$unwind: '$taggedClimbs'
},
{
$set: {
'climb.id': '$taggedClimbs._id',
'climb.name': '$taggedClimbs.name'
}
},
{
$unset: 'taggedClimbs'
},
{ $replaceRoot: { newRoot: '$climb' } }
]
}
},
{
$lookup: {
localField: 'name',
from: 'media', // Foreign collection name
foreignField: 'mediaUrl',
as: 'areaTags', // add a new parent field
pipeline: [
{
$lookup: {
from: 'areas', // other collection name
foreignField: 'metadata.area_id', // climb._id
localField: 'destinationId',
as: 'taggedAreas'
}

},
{
$unwind: '$taggedAreas'
},
{
$set: {
'area.id': '$taggedAreas.metadata.area_id',
'area.name': '$taggedAreas.area_name'
}
},
{
$unset: 'taggedAreas'
},
{ $replaceRoot: { newRoot: '$area' } }
]
}
}

]
)
return rs
}

/**
* Get a list of users and their tagged photo count
* @param limit how many entries
Expand Down Expand Up @@ -117,3 +203,36 @@ export default class MediaDataSource extends MongoDataSource<MediaType> {
return rs
}
}

/**
* A reusable Mongo aggregation snippet for 'joining' tag collection and media object collection.
* Ideally we should just embed tags as an array inside 'media_objects' collection to eliminate
* this extra join.
*
* ```
* select *
* from media, media_objects
* where media.mediaUrl == media_objects.name
* ```
*/
export const joiningTagWithMediaObject = [
{
$lookup: {
localField: 'mediaUrl',
from: 'media_objects', // Foreign collection name
foreignField: 'name',
as: 'meta' // add a new parent field
}
},
{ $unwind: '$meta' },
{
$unset: ['meta.name', 'meta._id', 'meta.createdAt', 'meta.updatedAt']
},
{
$replaceWith: {
$mergeObjects: ['$$ROOT', '$meta']
}
},
{
$unset: ['meta']
}]

0 comments on commit 1619caf

Please sign in to comment.