Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate photo storage to GCloud storage #273

Merged
merged 16 commits into from
May 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ MONGO_READ_PREFERENCE=primary
MONGO_REPLICA_SET_NAME=rs0
CONTENT_BASEDIR=./tmp
DEPLOYMENT_ENV=development
CDN_URL=https://storage.googleapis.com/openbeta-staging

# Typesense
TYPESENSE_NODE=4wknoyspjq6l7c9fp-1.a1.typesense.net
Expand Down
31 changes: 29 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@
"preLaunchTask": "tsc: build - tsconfig.json",
"outFiles": [
"${workspaceFolder}/build/**/*.js"
]
],
"console": "integratedTerminal",
},
{
"name": "Debug Jest Tests",
Expand All @@ -71,6 +72,32 @@
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
}
},
{
"type": "node",
"request": "launch",
"name": "Create MediaObjects collection",
"program": "${workspaceFolder}/src/db/utils/jobs/migration/CreateMediaMetaCollection.ts",
"preLaunchTask": "tsc: build - tsconfig.json",
"outFiles": [
"${workspaceFolder}/build/**/*.js"
],
"skipFiles": [
"<node_internals>/**"
],
},
{
"type": "node",
"request": "launch",
"name": "Migrate old tags to MediaObjects collection",
"program": "${workspaceFolder}/src/db/utils/jobs/migration/MigrateTagsToMediaCollection.ts",
"preLaunchTask": "tsc: build - tsconfig.json",
"outFiles": [
"${workspaceFolder}/build/**/*.js"
],
"skipFiles": [
"<node_internals>/**"
],
},
]
}
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
},
"dependencies": {
"@babel/runtime": "^7.17.2",
"@google-cloud/storage": "^6.9.5",
"@graphql-tools/schema": "^8.3.1",
"@openbeta/sandbag": "^0.0.37",
"@turf/area": "^6.5.0",
Expand All @@ -34,9 +35,12 @@
"@types/uuid": "^8.3.3",
"apollo-datasource-mongodb": "^0.5.4",
"apollo-server": "^3.9.0",
"axios": "^1.3.6",
"cors": "^2.8.5",
"dot-object": "^2.1.4",
"dotenv": "^10.0.0",
"expiry-map": "^2.0.0",
"glob": "^10.2.2",
"graphql": "^16.5.0",
"graphql-middleware": "^6.1.31",
"graphql-shield": "^7.5.0",
Expand All @@ -48,8 +52,10 @@
"mongoose-lean-virtuals": "0.9.1",
"node-fetch": "2",
"p-limit": "^4.0.0",
"p-memoize": "^7.1.1",
"pino": "^8.2.0",
"sanitize-html": "^2.7.2",
"sharp": "^0.32.0",
"typesense": "^1.2.1",
"underscore": "^1.13.2",
"uuid": "^8.3.2",
Expand Down
7 changes: 2 additions & 5 deletions src/db/AreaTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ClimbType } from './ClimbTypes.js'
import { ChangeRecordMetadataType } from './ChangeLogType.js'
import { GradeContexts } from '../GradeUtils.js'
import { ExperimentalAuthorType } from './UserTypes.js'
import { AuthorMetadata } from '../types.js'

/**
* Areas are a grouping mechanism in the OpenBeta data model that allow
Expand All @@ -32,7 +33,7 @@ export type AreaType = IAreaProps & {
* See AreaType for the reified version of this object, and always use it
* if you are working with data that exists inside the database.
*/
export interface IAreaProps {
export interface IAreaProps extends AuthorMetadata {
_id: mongoose.Types.ObjectId
/**
* ShortCodes are short, globally uniqe codes that identify significant climbing areas
Expand Down Expand Up @@ -99,10 +100,6 @@ export interface IAreaProps {
_change?: ChangeRecordMetadataType
/** Used to delete an area. See https://www.mongodb.com/docs/manual/core/index-ttl/ */
_deleting?: Date
createdAt?: Date
updatedAt?: Date
updatedBy?: MUUID
createdBy?: MUUID
}

export interface IAreaMetadata {
Expand Down
54 changes: 54 additions & 0 deletions src/db/MediaObjectSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import mongoose from 'mongoose'

import { MediaObject, EntityTag } from './MediaObjectTypes.js'
import { PointSchema } from './ClimbSchema.js'

const { Schema } = mongoose

const UUID_TYPE = {
type: 'object', value: { type: 'Buffer' }
}

const EntitySchema = new Schema<EntityTag>({
targetId: { ...UUID_TYPE, index: true },
climbName: { type: Schema.Types.String },
areaName: { type: Schema.Types.String, required: true },
type: { type: Schema.Types.Number, required: true },
ancestors: { type: Schema.Types.String, required: true, index: true },
lnglat: {
type: PointSchema,
index: '2dsphere'
}
}, { _id: true })

const schema = new Schema<MediaObject>({
userUuid: { ...UUID_TYPE, index: true },
mediaUrl: { type: Schema.Types.String, unique: true, index: true },
width: { type: Schema.Types.Number, required: true },
height: { type: Schema.Types.Number, required: true },
size: { type: Schema.Types.Number, required: true },
format: { type: Schema.Types.String, required: true },
entityTags: [EntitySchema]
}, { _id: true, timestamps: true })

/**
* Additional indices
*/
schema.index({
/**
* For filtering media objects with/without tags
*/
entityTags: 1,
/**
* For sorting media objects by insertion order
*/
createdAt: -1 // ascending, more recent first
})

/**
* Get media object model
* @returns MediaObjectType
*/
export const getMediaObjectModel = (): mongoose.Model<MediaObject> => {
return mongoose.model('media_objects', schema)
}
58 changes: 58 additions & 0 deletions src/db/MediaObjectTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { ObjectId } from 'mongoose'
import { MUUID } from 'uuid-mongodb'
import { Point } from '@turf/helpers'

export type ImageFormatType = 'jpeg' | 'png' | 'webp' | 'avif'

export interface MediaObject {
_id: ObjectId
userUuid: MUUID
mediaUrl: string
width: number
height: number
format: ImageFormatType
createdAt: Date
size: number
entityTags: EntityTag[]
}

export interface EntityTag {
_id: ObjectId
targetId: MUUID
type: number
ancestors: string
climbName?: string
areaName: string
lnglat: Point
}

export interface MediaByUsers {
username: string
userUuid: MUUID
mediaWithTags: MediaObject[]
}
export interface MediaForFeedInput {
uuidStr?: string
maxUsers?: number
maxFiles?: number
includesNoEntityTags?: boolean
}

export interface TagByUser {
username?: string
userUuid: MUUID
total: number
}

export interface AllTimeTagStats {
totalMediaWithTags: number
byUsers: TagByUser[]
}
export interface TagsLeaderboardType {
allTime: AllTimeTagStats
}

export interface UserMediaQueryInput {
userUuid: string
maxFiles?: number
}
4 changes: 4 additions & 0 deletions src/db/MediaSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ MediaSchema.virtual('area', {
MediaSchema.plugin(mongooseLeanVirtuals)
MediaSchema.index({ mediaUuid: 1, destinationId: 1 }, { unique: true })

/**
* @deprecated Superseded by MediaObjectSchema
* @param name
*/
export const getMediaModel = (name: string = 'media'): mongoose.Model<MediaType> => {
return mongoose.model(name, MediaSchema)
}
35 changes: 23 additions & 12 deletions src/db/MediaTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ import { MUUID } from 'uuid-mongodb'

import { AreaType } from './AreaTypes.js'
import { ClimbType } from './ClimbTypes.js'
import { MediaObject } from './MediaObjectTypes.js'

// Type for 'Media' collection schema
/**
* @deprecated to be removed in favor of MediaObject type
*/
export interface MediaType {
_id?: mongoose.Types.ObjectId
mediaUuid: MUUID
Expand All @@ -20,11 +23,21 @@ export enum RefModelType {
areas = 'areas'
}

export interface MediaListByAuthorType {
_id: string
tagList: MediaType[]
/**
* A tag with media metadata
*/
export type BaseTagType = MediaType & MediaObject

export interface CompleteAreaTag extends BaseTagType {
area: AreaType
}

export interface CompleteClimbTag extends BaseTagType {
climb: ClimbType
}

export type TagType = CompleteAreaTag | CompleteClimbTag

export interface MediaInputType {
mediaUuid: MUUID
mediaUrl: string
Expand All @@ -33,7 +46,10 @@ export interface MediaInputType {
destType: number
}

interface BaseTagType {
/**
* TODO: consolidate this type with BaseTagType
*/
interface LegacyBaseTagType {
_id: mongoose.Types.ObjectId
mediaUuid: MUUID
mediaUrl: string
Expand All @@ -42,11 +58,11 @@ interface BaseTagType {
onModel: RefModelType
}

export interface AreaTagType extends BaseTagType {
export interface AreaTagType extends LegacyBaseTagType {
area: AreaType
}

export interface ClimbTagType extends BaseTagType {
export interface ClimbTagType extends LegacyBaseTagType {
climb: ClimbType
}

Expand All @@ -58,8 +74,3 @@ export interface DeleteTagResult {
destType: number
destinationId: string
}

export interface TagsLeaderboardType {
userUuid: string
total: number
}
5 changes: 5 additions & 0 deletions src/db/XMediaSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ export const XMediaSchema = new Schema<XMediaType>({

XMediaSchema.plugin(mongooseLeanVirtuals)

/**
* @deprecated Superceded by MediaObjects
* @param name
* @returns
*/
export const getXMediaModel = (name: string = 'xmedia'): mongoose.Model<XMediaType> => {
return mongoose.model(name, XMediaSchema)
}
5 changes: 4 additions & 1 deletion src/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { enableAllPlugins } from 'immer'
import { getAreaModel } from './AreaSchema.js'
import { getClimbModel } from './ClimbSchema.js'
import { getMediaModel } from './MediaSchema.js'
import { getMediaObjectModel } from './MediaObjectSchema.js'
import { getOrganizationModel } from './OrganizationSchema.js'
import { getTickModel } from './TickSchema.js'
import { getXMediaModel } from './XMediaSchema.js'
Expand Down Expand Up @@ -71,6 +72,7 @@ export const createIndexes = async (): Promise<void> => {
await getTickModel().createIndexes()
await getXMediaModel().createIndexes()
await getPostModel().createIndexes()
await getMediaObjectModel().createIndexes()
}

export const gracefulExit = async (exitCode: number = 0): Promise<void> => {
Expand Down Expand Up @@ -98,5 +100,6 @@ export {
getChangeLogModel,
getXMediaModel,
getPostModel,
getExperimentalUserModel
getExperimentalUserModel,
getMediaObjectModel
}
8 changes: 8 additions & 0 deletions src/db/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { AuthorMetadata } from '../../types'

export const getAuthorMetadataFromBaseNode = ({ updatedAt, updatedBy, createdAt, createdBy }: AuthorMetadata): AuthorMetadata => ({
updatedAt,
updatedBy,
createdAt,
createdBy
})
Loading