universo-platform-3d
430 строк · 11.1 Кб
1import { PREMIUM_ACCESS } from './../option-sets/premium-tiers'2import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'3import * as mongoose from 'mongoose'4import { Document, SchemaTypes } from 'mongoose'5import { UserGroupInvite } from '../user-groups/user-group-invite.schema'6import { ApiProperty } from '@nestjs/swagger'7import { USER_AVATAR_TYPE } from '../option-sets/user-avatar-types'8import { CustomData } from '../custom-data/models/custom-data.schema'9import { UserCartItem, UserCartItemSchema } from './models/user-cart.schema'10import { UserRecents, UserRecentsSchema } from './models/user-recents.schema'11import {12UserMarketing,13UserMarketingSchema
14} from './models/user-marketing.schema'15
16export type UserDocument = User & Document17
18export const USER_VIRTUAL_PROPERTY_PUBLIC_ASSETS = 'publicAssets'19export const USER_VIRTUAL_PROPERTY_PUBLIC_GROUPS = 'publicGroups'20
21/**
22* Tutorial nested object. The generic approach is for all of these to be undefined. In the consuming app, check for truthiness of user.tutorial[propertyName], e.g. user.tutorial.shownFirstSpacePopupV1
23*/
24@Schema()25export class UserTutorial {26// be sure to keep UpdateUserTutorialDto up to date with these properties27@Prop({ type: Boolean })28shownFirstInSpacePopupV1?: boolean29@Prop({ type: Boolean })30shownFirstHomeScreenPopupV1?: boolean31@Prop({ type: Boolean })32shownWebAppPopupV1?: boolean33}
34
35// Properties must not be undefined so that getPublicPropertiesForMongooseQuery can work
36export class UserPublicData {37@ApiProperty({ type: 'string' }) // @ApiProperty must be included to be exposed by the API and flow to FE codegen38_id = '' // Must not be undefined39@ApiProperty({ type: Date })40createdAt = new Date()41@ApiProperty({ type: Date })42updatedAt = new Date()43@ApiProperty({ type: 'string' })44discordUserId? = ''45@ApiProperty({ type: 'string' })46isInternalAdmin = ''47@ApiProperty({ type: 'string' })48displayName = ''49@ApiProperty({ type: 'string' })50email = ''51@ApiProperty({ type: 'string' })52publicBio = ''53// We only need a string to decipher the current avatar.54@ApiProperty({ type: 'string' })55avatarUrl = ''56
57// TODO: We can probably get rid of avatar type and any variables specific to ready player me.58@ApiProperty({ enum: USER_AVATAR_TYPE })59avatarType = ''60@ApiProperty({ type: 'string' })61readyPlayerMeUrlGlb? = ''62@ApiProperty({ type: [String] })63readyPlayerMeAvatarUrls? = []64@ApiProperty({ type: 'string' })65polygonPublicKey? = ''66@ApiProperty({ type: 'string' })67ethereumPublicKey? = ''68@ApiProperty({ type: 'string' })69twitterUsername? = ''70@ApiProperty({ type: 'string' })71githubUsername? = ''72@ApiProperty({ type: 'string' })73instagramUsername? = ''74@ApiProperty({ type: 'string' })75youtubeChannel? = ''76@ApiProperty({ type: 'string' })77artStationUsername? = ''78@ApiProperty({ type: 'string' })79sketchfabUsername? = ''80@ApiProperty({ type: 'string' })81profileImage? = ''82@ApiProperty({ type: 'string' })83coverImage? = ''84@ApiProperty({ type: [String] })85sidebarTags? = []86
87/**88* Closed Beta
89*/
90@ApiProperty({ type: 'boolean' })91closedBetaHasClickedInterestedInBeta? = false92@ApiProperty({ type: 'boolean' })93closedBetaIsInClosedBeta? = false94
95/**96* Terms
97*/
98@ApiProperty({ type: 'boolean' })99termsAgreedtoClosedAlpha? = false100
101@ApiProperty({ type: 'boolean' })102termsAgreedtoGeneralTOSandPP? = false103
104@ApiProperty({ enum: PREMIUM_ACCESS, isArray: true })105premiumAccess: PREMIUM_ACCESS[] = []106}
107
108@Schema({109timestamps: true,110toJSON: {111virtuals: true112}113})114export class User {115@ApiProperty()116_id: string117@ApiProperty()118createdAt: Date // this is managed by mongoose timestamps: true, but defining it here so types will align119@ApiProperty()120updatedAt: Date // this is managed by mongoose timestamps: true, but defining it here so types will align121
122@Prop()123@ApiProperty()124firebaseUID: string125
126/**127* Whether the user is a Mirror admin, exposing admin functionality. This should ONLY be used if the person works for The Mirror.
128*/
129@Prop()130@ApiProperty()131isInternalAdmin: string132
133@Prop({134required: true,135minLength: 3,136maxLength: 40137})138@ApiProperty()139displayName: string140
141@Prop({ type: [mongoose.Schema.Types.ObjectId], ref: 'UserGroupInvite' })142@ApiProperty()143groupInvitations: UserGroupInvite[]144
145/**146* @description Note: Change of pattern: NOT using friends: User[] here since we don't want to always populate the friends. We can always add a friends getter if we want to populate them. I think this will be easier on type safety
147* @date 2023-06-22 23:42
148*/
149@Prop({150type: [mongoose.Schema.Types.ObjectId],151ref: 'User',152select: false,153default: []154})155@ApiProperty({156description: 'A list of User IDs of friends.'157})158friends?: mongoose.Schema.Types.ObjectId[]159
160/**161* @description A list of User IDs of friends.
162* Note: Change of pattern: NOT using friends: User[] here since we don't want to always populate the friends. We can always add a friends getter if we want to populate them. I think this will be easier on type safety
163* @date 2023-06-22 23:42
164*/
165@Prop({166type: [mongoose.Schema.Types.ObjectId],167ref: 'User',168select: false,169default: []170})171@ApiProperty({172description: 'A list of User IDs that this User has sent friend requests to'173})174sentFriendRequestsToUsers?: mongoose.Schema.Types.ObjectId[]175
176@Prop({177required: false,178type: UserRecentsSchema179})180@ApiProperty()181recents?: UserRecents182
183@Prop({184required: false,185type: UserMarketingSchema,186select: false187})188@ApiProperty()189marketing?: UserMarketing190
191@Prop({192required: false193})194@ApiProperty()195email: string196
197@Prop(SchemaTypes.Boolean)198@ApiProperty()199emailVerified: boolean200
201@Prop()202@ApiProperty()203publicBio: string204
205// We only need a string to decipher the current avatar.206@Prop()207@ApiProperty()208avatarUrl: string209
210// TODO: We can probably get rid of avatar type and any variables specific to ready player me.211/**212* The high-level currently selected character (e.g. between a Mirror avatar, Ready Player Me avatar, or something else).
213* ex: If USER_AVATAR_TYPE.READY_PLAYER_ME is selected, then the readyPlayerMeUrlGlb should be used
214*/
215@Prop({216required: true,217enum: USER_AVATAR_TYPE,218default: USER_AVATAR_TYPE.MIRROR_AVATAR_V1,219type: String220})221@ApiProperty({ enum: USER_AVATAR_TYPE })222avatarType: string223
224/**225* @description I was previously adding this as an array, but it became super complex with needing to filter through to just find small pieces of data. We can always add support for arrays of CustomData in the future, but it's much simpler for now to 1 CustomData object per entity. Plus, it can have JSON, so array data can still be stored - it just won't be an array of CustomData types (rather, it will be an array of what the user specifies: string, numbers, etc. Note that we also want to support references to other entities: this will likely be via CustomDataEntityReference or something similar).
226* @date 2023-03-03 20:11
227*/
228@Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'CustomData' })229@ApiProperty()230customData: CustomData231
232@Prop({233required: true,234default: {}235})236tutorial: UserTutorial237
238/**239* The current string url ending in .glb
240*/
241@Prop()242@ApiProperty()243readyPlayerMeUrlGlb?: string244
245/**246* Array of avatar IDs
247*/
248@Prop([String])249@ApiProperty()250readyPlayerMeAvatarUrls: string[]251
252/**253* Closed Beta
254* @deprecated use premiumAccess
255*/
256@Prop({257default: false258})259@ApiProperty()260closedBetaHasClickedInterestedInBeta?: boolean261
262/**263* @deprecated use premiumAccess
264*/
265@Prop({266default: false267})268@ApiProperty()269closedBetaIsInClosedBeta?: boolean270
271/**272* Terms: Closed Alpha
273* Future properties can start with `terms` as well, such as termsAgreedToOpenAlpha, termsAgreedToGeneralTOS, etc.
274*/
275@Prop({276default: false277})278@ApiProperty({279description: 'Whether the user has agreed to the closed alpha agreement'280})281termsAgreedtoClosedAlpha?: boolean282
283@Prop({284default: false285})286@ApiProperty({287description:288'Whether the user has agreed to the general Terms of Service and Privacy Policy'289})290termsAgreedtoGeneralTOSandPP?: boolean291
292/**293* Social
294*/
295@Prop()296@ApiProperty()297discordUserId?: string298
299@Prop()300@ApiProperty()301polygonPublicKey?: string302
303@Prop()304@ApiProperty()305ethereumPublicKey?: string306
307@Prop()308@ApiProperty()309twitterUsername?: string310
311@Prop()312@ApiProperty()313githubUsername?: string314
315@Prop()316@ApiProperty()317instagramUsername?: string318
319@Prop()320@ApiProperty()321youtubeChannel?: string322
323@Prop()324@ApiProperty()325artStationUsername?: string326
327@Prop()328@ApiProperty()329sketchfabUsername?: string330
331/**332* Premium access
333* @description Premium access. Note that users can have MULTIPLE. This is important because we want to continually store whether they were in closed alpha. Plus, even with premium tiers, we'll have some "super premium" users too, enterprise users, etc.
334*/
335@Prop({336type: [String]337// enum: Object.keys(PREMIUM_ACCESS) // I tried using this here at one point but ran into issues. However, we do want to restrict the strings to enum values, so keeping this comment here.338})339@ApiProperty({ enum: PREMIUM_ACCESS })340premiumAccess: PREMIUM_ACCESS[]341
342/**343* Stripe
344*/
345@Prop()346@ApiProperty()347stripeCustomerId?: string348
349@Prop()350@ApiProperty()351stripeAccountId?: string352
353/**354* Deep Linking a key-value pair, e.g. spaceId and 1234-5678-abcd-efgh
355*/
356@Prop({357required: false358})359@ApiProperty()360deepLinkKey?: string361
362@Prop({363required: false364})365@ApiProperty()366deepLinkValue?: string367
368@Prop({369required: false370})371@ApiProperty()372deepLinkLastUpdatedAt?: Date373
374/**375* Profile Images
376*/
377@Prop()378@ApiProperty()379profileImage?: string380
381@Prop()382@ApiProperty()383coverImage?: string384
385/**386* Cart
387*/
388@Prop({389type: [UserCartItemSchema],390required: false,391select: false // note that select is false here so we don't return too many things for user by default392})393@ApiProperty({ type: () => UserCartItem })394cartItems?: UserCartItem[]395
396/**397* Premium Access ID used for subscription handling.
398*/
399
400@Prop()401@ApiProperty()402stripeSubscriptionId?: string403
404@Prop({ required: false, type: [String] })405@ApiProperty({ required: false })406sidebarTags?: string[]407
408@Prop({ required: false, type: Date })409@ApiProperty({ required: false })410lastActiveTimestamp?: Date411
412/**413* @description When a user requests to delete an account, it is marked with the deleted: true property. A user with the deleted: true property will be filtered out of all search results
414* @date 2023-12-22 17:31
415*/
416
417@Prop({ required: false, type: Boolean })418@ApiProperty({ required: false })419deleted?: boolean420}
421
422export const UserSchema = SchemaFactory.createForClass(User)423
424// Specifying a virtual with a `ref` property is how you enable virtual
425// population
426UserSchema.virtual(USER_VIRTUAL_PROPERTY_PUBLIC_ASSETS, {427ref: 'Asset',428localField: '_id',429foreignField: 'owner'430})431