From 0daaa9088549e6752d96032bfa16b000be81283f Mon Sep 17 00:00:00 2001 From: hanzlamateen Date: Tue, 11 Jul 2023 18:24:07 +0500 Subject: [PATCH 1/7] Changes for avatar service to use schema, knex & feathers 5 --- .../src/admin/common/variables/avatar.ts | 4 +- .../admin/components/Avatars/AvatarDrawer.tsx | 4 +- .../admin/components/Avatars/AvatarTable.tsx | 6 +- .../src/admin/services/AvatarService.ts | 19 +- .../ui/ProfileDetailView/SelectAvatarMenu.tsx | 16 +- .../UserMenu/menus/AvatarModifyMenu.tsx | 4 +- .../user/functions/useUserAvatarThumbnail.ts | 3 +- .../src/user/services/AvatarService.ts | 55 ++---- .../common/src/dbmodels/AvatarResource.ts | 35 ---- packages/common/src/dbmodels/UserInterface.ts | 11 ++ .../common/src/interfaces/AvatarInterface.ts | 39 ---- .../common/src/interfaces/AvatarResult.ts | 33 ---- .../src/interfaces/StaticResourceInterface.ts | 2 + packages/common/src/interfaces/User.ts | 15 +- .../src/avatar/state/AvatarNetworkState.tsx | 3 +- .../src/networking/interfaces/WorldState.ts | 7 +- .../schemas/media/static-resource.schema.ts | 123 ++++++++++++ .../engine/src/schemas/user/avatar.schema.ts | 111 +++++++++++ .../src/hooks/verify-scope.test.ts | 9 +- .../project-permission.test.ts | 3 +- .../src/projects/project/project.class.ts | 49 +++-- .../src/social/party-user/party-user.class.ts | 5 +- .../social/party-user/party-user.service.ts | 5 +- .../src/social/party/party.test.ts | 3 +- .../src/user/avatar/avatar-helper.ts | 52 +++--- .../src/user/avatar/avatar.class.ts | 176 +++++++++--------- .../src/user/avatar/avatar.docs.ts | 38 ++-- .../src/user/avatar/avatar.hooks.ts | 44 ++++- .../src/user/avatar/avatar.model.ts | 83 --------- .../src/user/avatar/avatar.resolvers.ts | 67 +++++++ .../avatar/{avatar.service.ts => avatar.ts} | 27 +-- .../identity-provider.class.ts | 6 +- packages/server-core/src/user/services.ts | 2 +- .../src/user/strategies/discord.ts | 6 +- .../src/user/strategies/facebook.ts | 6 +- .../server-core/src/user/strategies/github.ts | 5 +- .../server-core/src/user/strategies/google.ts | 4 +- .../src/user/strategies/linkedin.ts | 6 +- .../src/user/strategies/twitter.ts | 6 +- .../server-core/src/user/user/user.model.ts | 57 +++++- .../server-core/src/user/user/user.test.ts | 5 +- scripts/create-build-status.ts | 5 +- scripts/fetch-helm-versions.ts | 105 +++++------ scripts/record-build-error.ts | 5 +- scripts/record-build-success.ts | 5 +- 45 files changed, 730 insertions(+), 544 deletions(-) delete mode 100644 packages/common/src/dbmodels/AvatarResource.ts delete mode 100755 packages/common/src/interfaces/AvatarInterface.ts delete mode 100755 packages/common/src/interfaces/AvatarResult.ts create mode 100755 packages/engine/src/schemas/media/static-resource.schema.ts create mode 100644 packages/engine/src/schemas/user/avatar.schema.ts delete mode 100755 packages/server-core/src/user/avatar/avatar.model.ts create mode 100644 packages/server-core/src/user/avatar/avatar.resolvers.ts rename packages/server-core/src/user/avatar/{avatar.service.ts => avatar.ts} (73%) diff --git a/packages/client-core/src/admin/common/variables/avatar.ts b/packages/client-core/src/admin/common/variables/avatar.ts index 5a9bec5f054..e56c51903aa 100644 --- a/packages/client-core/src/admin/common/variables/avatar.ts +++ b/packages/client-core/src/admin/common/variables/avatar.ts @@ -23,7 +23,7 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { AvatarInterface } from '@etherealengine/common/src/interfaces/AvatarInterface' +import { AvatarType } from '@etherealengine/engine/src/schemas/user/avatar.schema' export interface AvatarColumn { id: 'select' | 'id' | 'name' | 'owner' | 'thumbnail' | 'action' @@ -51,7 +51,7 @@ export const avatarColumns: AvatarColumn[] = [ ] export interface AvatarData { - el: AvatarInterface + el: AvatarType select: JSX.Element id: string name: string | undefined diff --git a/packages/client-core/src/admin/components/Avatars/AvatarDrawer.tsx b/packages/client-core/src/admin/components/Avatars/AvatarDrawer.tsx index b0600a9cb70..016cdbf4713 100755 --- a/packages/client-core/src/admin/components/Avatars/AvatarDrawer.tsx +++ b/packages/client-core/src/admin/components/Avatars/AvatarDrawer.tsx @@ -42,10 +42,10 @@ import { THUMBNAIL_HEIGHT, THUMBNAIL_WIDTH } from '@etherealengine/common/src/constants/AvatarConstants' -import { AvatarInterface } from '@etherealengine/common/src/interfaces/AvatarInterface' import { AssetLoader } from '@etherealengine/engine/src/assets/classes/AssetLoader' import { AvatarRigComponent } from '@etherealengine/engine/src/avatar/components/AvatarAnimationComponent' import { getOptionalComponent } from '@etherealengine/engine/src/ecs/functions/ComponentFunctions' +import { AvatarType } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { dispatchAction, getMutableState, useHookstate } from '@etherealengine/hyperflux' import Box from '@etherealengine/ui/src/primitives/mui/Box' import Button from '@etherealengine/ui/src/primitives/mui/Button' @@ -81,7 +81,7 @@ enum ConfirmState { interface Props { open: boolean mode: AvatarDrawerMode - selectedAvatar?: AvatarInterface + selectedAvatar?: AvatarType onClose: () => void } diff --git a/packages/client-core/src/admin/components/Avatars/AvatarTable.tsx b/packages/client-core/src/admin/components/Avatars/AvatarTable.tsx index ba4153c867a..529e56dfb5d 100755 --- a/packages/client-core/src/admin/components/Avatars/AvatarTable.tsx +++ b/packages/client-core/src/admin/components/Avatars/AvatarTable.tsx @@ -27,7 +27,7 @@ import React, { useEffect } from 'react' import { useTranslation } from 'react-i18next' import ConfirmDialog from '@etherealengine/client-core/src/common/components/ConfirmDialog' -import { AvatarInterface } from '@etherealengine/common/src/interfaces/AvatarInterface' +import { AvatarType } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { getMutableState, useHookstate } from '@etherealengine/hyperflux' import Box from '@etherealengine/ui/src/primitives/mui/Box' import Checkbox from '@etherealengine/ui/src/primitives/mui/Checkbox' @@ -63,7 +63,7 @@ const AvatarTable = ({ className, search, selectedAvatarIds, setSelectedAvatarId const fieldOrder = useHookstate('asc') const sortField = useHookstate('name') const openAvatarDrawer = useHookstate(false) - const avatarData = useHookstate(null) + const avatarData = useHookstate(null) const handlePageChange = (event: unknown, newPage: number) => { AdminAvatarService.fetchAdminAvatars(newPage, search, sortField.value, fieldOrder.value) @@ -97,7 +97,7 @@ const AvatarTable = ({ className, search, selectedAvatarIds, setSelectedAvatarId } } - const createData = (el: AvatarInterface): AvatarData => { + const createData = (el: AvatarType): AvatarData => { return { el, select: ( diff --git a/packages/client-core/src/admin/services/AvatarService.ts b/packages/client-core/src/admin/services/AvatarService.ts index 738cabd19d9..84676b7c85b 100644 --- a/packages/client-core/src/admin/services/AvatarService.ts +++ b/packages/client-core/src/admin/services/AvatarService.ts @@ -25,11 +25,10 @@ Ethereal Engine. All Rights Reserved. import { Paginated } from '@feathersjs/feathers' -import { AvatarInterface } from '@etherealengine/common/src/interfaces/AvatarInterface' -import { AvatarResult } from '@etherealengine/common/src/interfaces/AvatarResult' import { StaticResourceInterface } from '@etherealengine/common/src/interfaces/StaticResourceInterface' import multiLogger from '@etherealengine/common/src/logger' import { matches, Validator } from '@etherealengine/engine/src/common/functions/MatchesUtils' +import { avatarPath, AvatarType } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { defineAction, defineState, dispatchAction, getMutableState } from '@etherealengine/hyperflux' import { API } from '../../API' @@ -42,7 +41,7 @@ export const AVATAR_PAGE_LIMIT = 100 export const AdminAvatarState = defineState({ name: 'AdminAvatarState', initial: () => ({ - avatars: [] as Array, + avatars: [] as Array, thumbnail: undefined as StaticResourceInterface | undefined, skip: 0, limit: AVATAR_PAGE_LIMIT, @@ -92,14 +91,14 @@ export const AdminAvatarReceptors = { //Service export const AdminAvatarService = { - fetchAdminAvatars: async (skip = 0, search: string | null = null, sortField = 'name', orderBy = 'asc') => { - let sortData = {} + fetchAdminAvatars: async (skip = 0, search: string | undefined = undefined, sortField = 'name', orderBy = 'asc') => { + const sortData = {} if (sortField.length > 0) { - sortData[sortField] = orderBy === 'desc' ? 0 : 1 + sortData[sortField] = orderBy === 'desc' ? -1 : 1 } const adminAvatarState = getMutableState(AdminAvatarState) const limit = adminAvatarState.limit.value - const avatars = (await API.instance.client.service('avatar').find({ + const avatars = await API.instance.client.service(avatarPath).find({ query: { admin: true, $sort: { @@ -109,12 +108,12 @@ export const AdminAvatarService = { $skip: skip * AVATAR_PAGE_LIMIT, search: search } - })) as Paginated + }) dispatchAction(AdminAvatarActions.avatarsFetched({ avatars })) }, removeAdminAvatar: async (id: string) => { try { - await API.instance.client.service('avatar').remove(id) + await API.instance.client.service(avatarPath).remove(id) dispatchAction(AdminAvatarActions.avatarRemoved({})) } catch (err) { logger.error(err) @@ -126,7 +125,7 @@ export const AdminAvatarService = { export class AdminAvatarActions { static avatarsFetched = defineAction({ type: 'ee.client.AdminAvatar.AVATARS_RETRIEVED' as const, - avatars: matches.object as Validator + avatars: matches.object as Validator> }) static avatarCreated = defineAction({ diff --git a/packages/client-core/src/systems/ui/ProfileDetailView/SelectAvatarMenu.tsx b/packages/client-core/src/systems/ui/ProfileDetailView/SelectAvatarMenu.tsx index 10d8f8ec697..ddb25377e2e 100644 --- a/packages/client-core/src/systems/ui/ProfileDetailView/SelectAvatarMenu.tsx +++ b/packages/client-core/src/systems/ui/ProfileDetailView/SelectAvatarMenu.tsx @@ -27,10 +27,10 @@ import { createState, useHookstate } from '@hookstate/core' import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import { AvatarInterface } from '@etherealengine/common/src/interfaces/AvatarInterface' import { AvatarEffectComponent } from '@etherealengine/engine/src/avatar/components/AvatarEffectComponent' import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine' import { hasComponent } from '@etherealengine/engine/src/ecs/functions/ComponentFunctions' +import { AvatarType } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { createXRUI } from '@etherealengine/engine/src/xrui/functions/createXRUI' import { WidgetAppService } from '@etherealengine/engine/src/xrui/WidgetAppService' import { WidgetName } from '@etherealengine/engine/src/xrui/Widgets' @@ -62,7 +62,7 @@ const SelectAvatarMenu = () => { const [page, setPage] = useState(0) const [imgPerPage, setImgPerPage] = useState(Math.min(getAvatarPerPage(), avatarState.total.value)) - const [selectedAvatar, setSelectedAvatar] = useState('') + const [selectedAvatar, setSelectedAvatar] = useState(undefined) useEffect(() => { AvatarService.fetchAvatarList() @@ -93,14 +93,14 @@ const SelectAvatarMenu = () => { } const confirmAvatar = () => { - if (selectedAvatar && avatarId != selectedAvatar?.avatar?.name) { + if (selectedAvatar && avatarId != selectedAvatar?.name) { setAvatar(selectedAvatar?.id || '') WidgetAppService.setWidgetVisibility(WidgetName.PROFILE, false) } - setSelectedAvatar('') + setSelectedAvatar(undefined) } - const selectAvatar = (avatarResources: AvatarInterface) => { + const selectAvatar = (avatarResources: AvatarType) => { setSelectedAvatar(avatarResources) } @@ -125,7 +125,7 @@ const SelectAvatarMenu = () => { key={avatar.id} xr-layer="true" onClick={() => selectAvatar(avatar)} - className={`paperAvatar ${avatar.name == selectedAvatar?.avatar?.name ? 'selectedAvatar' : ''} + className={`paperAvatar ${avatar.name == selectedAvatar?.name ? 'selectedAvatar' : ''} ${avatar.name == avatarId ? 'activeAvatar' : ''}`} style={{ pointerEvents: avatar.name == avatarId ? 'none' : 'auto', @@ -175,7 +175,7 @@ const SelectAvatarMenu = () => { xr-layer="true" backgroundColor="#f87678" onClick={() => { - setSelectedAvatar('') + setSelectedAvatar(undefined) }} disabled={!selectedAvatar} content={X} @@ -184,7 +184,7 @@ const SelectAvatarMenu = () => { xr-layer="true" backgroundColor="#23af3a" onClick={confirmAvatar} - disabled={selectedAvatar?.avatar?.name == avatarId} + disabled={selectedAvatar?.name == avatarId} content={} /> { useEffect(() => { if (!userAvatarState.avatarID?.value) return Engine.instance.api - .service('avatar') + .service(avatarPath) .get(userAvatarState.avatarID.value) .then((avatarDetails) => { avatarState.set(avatarDetails.thumbnailResource?.url ?? DEFAULT_PROFILE_IMG_PLACEHOLDER) diff --git a/packages/client-core/src/user/services/AvatarService.ts b/packages/client-core/src/user/services/AvatarService.ts index f0968975dbb..022b705de9e 100644 --- a/packages/client-core/src/user/services/AvatarService.ts +++ b/packages/client-core/src/user/services/AvatarService.ts @@ -23,22 +23,15 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { Paginated } from '@feathersjs/feathers' -import axios from 'axios' -import i18n from 'i18next' - -import config from '@etherealengine/common/src/config' -import { AvatarInterface } from '@etherealengine/common/src/interfaces/AvatarInterface' import { EntityUUID } from '@etherealengine/common/src/interfaces/EntityUUID' import { StaticResourceInterface } from '@etherealengine/common/src/interfaces/StaticResourceInterface' import { UserId } from '@etherealengine/common/src/interfaces/UserId' import { AvatarNetworkAction } from '@etherealengine/engine/src/avatar/state/AvatarNetworkState' import { matches, Validator } from '@etherealengine/engine/src/common/functions/MatchesUtils' import { Engine } from '@etherealengine/engine/src/ecs/classes/Engine' -import { WorldNetworkAction } from '@etherealengine/engine/src/networking/functions/WorldNetworkAction' +import { avatarPath, AvatarType } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { defineAction, defineState, dispatchAction, getMutableState, getState } from '@etherealengine/hyperflux' -import { NotificationService } from '../../common/services/NotificationService' import { uploadToFeathersService } from '../../util/upload' import { AuthAction, AuthState } from './AuthService' @@ -48,7 +41,7 @@ export const AVATAR_PAGE_LIMIT = 100 export const AvatarState = defineState({ name: 'AvatarState', initial: () => ({ - avatarList: [] as Array, + avatarList: [] as Array, search: undefined as string | undefined, skip: 0, limit: AVATAR_PAGE_LIMIT, @@ -73,7 +66,7 @@ export const AvatarServiceReceptor = (action) => { export const AvatarService = { async createAvatar(model: File, thumbnail: File, avatarName: string, isPublic: boolean) { - const newAvatar = await Engine.instance.api.service('avatar').create({ + const newAvatar = await Engine.instance.api.service(avatarPath).create({ name: avatarName, isPublic }) @@ -91,20 +84,20 @@ export const AvatarService = { const skip = getState(AvatarState).skip const newSkip = incDec === 'increment' ? skip + AVATAR_PAGE_LIMIT : incDec === 'decrement' ? skip - AVATAR_PAGE_LIMIT : skip - const result = (await Engine.instance.api.service('avatar').find({ + const result = await Engine.instance.api.service(avatarPath).find({ query: { search, $skip: newSkip, $limit: AVATAR_PAGE_LIMIT } - })) as Paginated + }) dispatchAction( AvatarActions.updateAvatarListAction({ avatarList: result.data, search, skip: result.skip, total: result.total }) ) }, async patchAvatar( - originalAvatar: AvatarInterface, + originalAvatar: AvatarType, avatarName: string, updateModels: boolean, avatarFile?: File, @@ -124,7 +117,8 @@ export const AvatarService = { originalAvatar.isPublic, originalAvatar.id ) - const removalPromises = [] as any + + const removalPromises: Promise[] = [] if (uploadResponse[0].id !== originalAvatar.modelResourceId) removalPromises.push(AvatarService.removeStaticResource(originalAvatar.modelResourceId)) if (uploadResponse[1].id !== originalAvatar.thumbnailResourceId) @@ -138,7 +132,7 @@ export const AvatarService = { } } - const avatar = await Engine.instance.api.service('avatar').patch(originalAvatar.id, payload) + const avatar = await Engine.instance.api.service(avatarPath).patch(originalAvatar.id, payload) dispatchAction(AvatarActions.updateAvatarAction({ avatar })) const authState = getState(AuthState) @@ -149,12 +143,6 @@ export const AvatarService = { } }, - async removeAvatar(keys: string) { - await Engine.instance.api.service('avatar').remove('', { query: { keys } }) - NotificationService.dispatchNotify(i18n.t('user:avatar.remove-success-msg'), { variant: 'success' }) - return this.fetchAvatarList() - }, - async removeStaticResource(id: string) { return Engine.instance.api.service('static-resource').remove(id) }, @@ -171,25 +159,6 @@ export const AvatarService = { ) }, - async uploadAvatar(data: any) { - const authState = getState(AuthState) - const token = authState.authUser.accessToken - const selfUser = authState.user - const res = await axios.post(`https://${config.client.serverHost}/upload`, data, { - headers: { - 'Content-Type': 'multipart/form-data', - Authorization: 'Bearer ' + token - } - }) - const userId = selfUser.id ?? null - await Engine.instance.api.service('user').patch(userId, { - name: selfUser.name - }) - const result = res.data - NotificationService.dispatchNotify('Avatar updated', { variant: 'success' }) - dispatchAction(AuthAction.avatarUpdatedAction({ url: result.url })) - }, - async uploadAvatarModel(avatar: File, thumbnail: File, avatarName: string, isPublic: boolean, avatarId?: string) { return uploadToFeathersService('upload-asset', [avatar, thumbnail], { type: 'user-avatar-upload', @@ -203,7 +172,7 @@ export const AvatarService = { async getAvatar(id: string) { try { - return Engine.instance.api.service('avatar').get(id) + return Engine.instance.api.service(avatarPath).get(id) } catch (err) { return null } @@ -213,13 +182,13 @@ export const AvatarService = { export class AvatarActions { static updateAvatarListAction = defineAction({ type: 'ee.client.avatar.AVATAR_FETCHED' as const, - avatarList: matches.array as Validator, + avatarList: matches.array as Validator, search: matches.string.optional(), skip: matches.number, total: matches.number }) static updateAvatarAction = defineAction({ type: 'ee.client.avatar.AVATAR_UPDATED' as const, - avatar: matches.object as Validator + avatar: matches.object as Validator }) } diff --git a/packages/common/src/dbmodels/AvatarResource.ts b/packages/common/src/dbmodels/AvatarResource.ts deleted file mode 100644 index e8e905aeb91..00000000000 --- a/packages/common/src/dbmodels/AvatarResource.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* -CPAL-1.0 License - -The contents of this file are subject to the Common Public Attribution License -Version 1.0. (the "License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at -https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. -The License is based on the Mozilla Public License Version 1.1, but Sections 14 -and 15 have been added to cover use of software over a computer network and -provide for limited attribution for the Original Developer. In addition, -Exhibit A has been modified to be consistent with Exhibit B. - -Software distributed under the License is distributed on an "AS IS" basis, -WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the -specific language governing rights and limitations under the License. - -The Original Code is Ethereal Engine. - -The Original Developer is the Initial Developer. The Initial Developer of the -Original Code is the Ethereal Engine team. - -All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 -Ethereal Engine. All Rights Reserved. -*/ - -export interface AvatarInterface { - id: string - name: string - identifierName: string - modelResourceId: string - thumbnailResourceId: string - isPublic: boolean - userId: string - project?: string -} diff --git a/packages/common/src/dbmodels/UserInterface.ts b/packages/common/src/dbmodels/UserInterface.ts index d3a56da0a09..c77e0abe14a 100644 --- a/packages/common/src/dbmodels/UserInterface.ts +++ b/packages/common/src/dbmodels/UserInterface.ts @@ -30,3 +30,14 @@ export interface UserInterface { isGuest: boolean did: string } + +export interface AvatarInterface { + id: string + name: string + identifierName: string + modelResourceId: string + thumbnailResourceId: string + isPublic: boolean + userId: string + project?: string +} diff --git a/packages/common/src/interfaces/AvatarInterface.ts b/packages/common/src/interfaces/AvatarInterface.ts deleted file mode 100755 index 5de05af233c..00000000000 --- a/packages/common/src/interfaces/AvatarInterface.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* -CPAL-1.0 License - -The contents of this file are subject to the Common Public Attribution License -Version 1.0. (the "License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at -https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. -The License is based on the Mozilla Public License Version 1.1, but Sections 14 -and 15 have been added to cover use of software over a computer network and -provide for limited attribution for the Original Developer. In addition, -Exhibit A has been modified to be consistent with Exhibit B. - -Software distributed under the License is distributed on an "AS IS" basis, -WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the -specific language governing rights and limitations under the License. - -The Original Code is Ethereal Engine. - -The Original Developer is the Initial Developer. The Initial Developer of the -Original Code is the Ethereal Engine team. - -All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 -Ethereal Engine. All Rights Reserved. -*/ - -import { StaticResourceInterface } from './StaticResourceInterface' - -export type AvatarInterface = { - id: string - name: string - isPublic: boolean - userId: string - modelResourceId: string - thumbnailResourceId: string - identifierName: string - modelResource?: StaticResourceInterface - thumbnailResource?: StaticResourceInterface - project?: string -} diff --git a/packages/common/src/interfaces/AvatarResult.ts b/packages/common/src/interfaces/AvatarResult.ts deleted file mode 100755 index 4a3e1206483..00000000000 --- a/packages/common/src/interfaces/AvatarResult.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* -CPAL-1.0 License - -The contents of this file are subject to the Common Public Attribution License -Version 1.0. (the "License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at -https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. -The License is based on the Mozilla Public License Version 1.1, but Sections 14 -and 15 have been added to cover use of software over a computer network and -provide for limited attribution for the Original Developer. In addition, -Exhibit A has been modified to be consistent with Exhibit B. - -Software distributed under the License is distributed on an "AS IS" basis, -WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the -specific language governing rights and limitations under the License. - -The Original Code is Ethereal Engine. - -The Original Developer is the Initial Developer. The Initial Developer of the -Original Code is the Ethereal Engine team. - -All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 -Ethereal Engine. All Rights Reserved. -*/ - -import { AvatarInterface } from './AvatarInterface' - -export type AvatarResult = { - data: AvatarInterface[] - total: number - limit: number - skip: number -} diff --git a/packages/common/src/interfaces/StaticResourceInterface.ts b/packages/common/src/interfaces/StaticResourceInterface.ts index e01296704e8..96bf75d0708 100755 --- a/packages/common/src/interfaces/StaticResourceInterface.ts +++ b/packages/common/src/interfaces/StaticResourceInterface.ts @@ -40,6 +40,8 @@ export interface StaticResourceInterface { tags?: string[] url: string stats?: Record + createdAt: string + updatedAt: string } export interface StaticResourceCreateInterface {} diff --git a/packages/common/src/interfaces/User.ts b/packages/common/src/interfaces/User.ts index 6a86e77d86e..467b1a0b099 100755 --- a/packages/common/src/interfaces/User.ts +++ b/packages/common/src/interfaces/User.ts @@ -24,9 +24,7 @@ Ethereal Engine. All Rights Reserved. */ import { AdminScopeType } from './AdminScopeType' -import { AvatarInterface } from './AvatarInterface' import { IdentityProvider } from './IdentityProvider' -import { Instance } from './Instance' import { InstanceAttendanceInterface } from './InstanceAttendance' import { LocationAdmin } from './LocationAdmin' import { LocationBan } from './LocationBan' @@ -109,6 +107,19 @@ export interface CreateEditUser { scopes?: UserScope[] | AdminScopeType[] } +type AvatarInterface = { + id: string + name: string + isPublic: boolean + userId: string + modelResourceId: string + thumbnailResourceId: string + identifierName: string + modelResource?: StaticResourceInterface + thumbnailResource?: StaticResourceInterface + project?: string +} + export function resolveUser(user: any): UserInterface { let returned = user if (user?.identity_providers) { diff --git a/packages/engine/src/avatar/state/AvatarNetworkState.tsx b/packages/engine/src/avatar/state/AvatarNetworkState.tsx index c9c979aa859..16df6c52fee 100644 --- a/packages/engine/src/avatar/state/AvatarNetworkState.tsx +++ b/packages/engine/src/avatar/state/AvatarNetworkState.tsx @@ -38,6 +38,7 @@ import { NetworkObjectComponent } from '../../networking/components/NetworkObjec import { WorldNetworkAction } from '../../networking/functions/WorldNetworkAction' import { WorldState } from '../../networking/interfaces/WorldState' import { UUIDComponent } from '../../scene/components/UUIDComponent' +import { avatarPath } from '../../schemas/user/avatar.schema' import { changeAvatarAnimationState } from '../animation/AvatarAnimationGraph' import { AvatarStates, matchesAvatarState } from '../animation/Util' import { loadAvatarForUser } from '../functions/avatarFunctions' @@ -148,7 +149,7 @@ const AvatarReactor = React.memo(({ entityUUID }: { entityUUID: EntityUUID }) => if (!state.avatarID.value) return Engine.instance.api - .service('avatar') + .service(avatarPath) .get(state.avatarID.value) .then((avatarDetails) => { if (!avatarDetails.modelResource?.url) return diff --git a/packages/engine/src/networking/interfaces/WorldState.ts b/packages/engine/src/networking/interfaces/WorldState.ts index 71b300b90d3..e0f8462a3c4 100755 --- a/packages/engine/src/networking/interfaces/WorldState.ts +++ b/packages/engine/src/networking/interfaces/WorldState.ts @@ -23,18 +23,17 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import matches from 'ts-matches' - -import { AvatarInterface } from '@etherealengine/common/src/interfaces/AvatarInterface' import { UserId } from '@etherealengine/common/src/interfaces/UserId' import { defineState } from '@etherealengine/hyperflux' +import { AvatarType } from '../../schemas/user/avatar.schema' + export const WorldState = defineState({ name: 'WorldState', initial: () => ({ /** a history of user names - does not get cleaned up upon a user leaving the world */ userNames: {} as Record, /** @deprecated */ - userAvatarDetails: {} as Record + userAvatarDetails: {} as Record }) }) diff --git a/packages/engine/src/schemas/media/static-resource.schema.ts b/packages/engine/src/schemas/media/static-resource.schema.ts new file mode 100755 index 00000000000..2478b4c6ea2 --- /dev/null +++ b/packages/engine/src/schemas/media/static-resource.schema.ts @@ -0,0 +1,123 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +// For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html +import { querySyntax, Type } from '@feathersjs/typebox' +import type { Static } from '@feathersjs/typebox' + +export const staticResourcePath = 'static-resource' + +export const staticResourceMethods = ['find', 'get', 'create', 'patch', 'remove'] as const + +// Main data model schema +export const staticResourceSchema = Type.Object( + { + id: Type.String({ + format: 'uuid' + }), + sid: Type.String(), + key: Type.String(), + metadata: Type.Any(), + mimeType: Type.String(), + userId: Type.String({ + format: 'uuid' + }), + hash: Type.String(), + project: Type.String(), + driver: Type.String(), + attribution: Type.String(), + licensing: Type.String(), + tags: Type.Array(Type.String()), + url: Type.String(), + stats: Type.Record(Type.String(), Type.Any()), + createdAt: Type.String({ format: 'date-time' }), + updatedAt: Type.String({ format: 'date-time' }) + }, + { $id: 'StaticResource', additionalProperties: false } +) +export type StaticResourceType = Static + +export type StaticResourceDatabaseType = Omit & { + metadata: string + tags: string + stats: string +} + +// Schema for creating new entries +export const staticResourceDataSchema = Type.Pick( + staticResourceSchema, + [ + 'sid', + 'key', + 'metadata', + 'mimeType', + 'userId', + 'hash', + 'project', + 'driver', + 'attribution', + 'licensing', + 'tags', + 'url', + 'stats' + ], + { + $id: 'StaticResourceData' + } +) +export type StaticResourceData = Static + +// Schema for updating existing entries +export const staticResourcePatchSchema = Type.Partial(staticResourceSchema, { + $id: 'StaticResourcePatch' +}) +export type StaticResourcePatch = Static + +// Schema for allowed query properties +export const staticResourceQueryProperties = Type.Pick(staticResourceSchema, [ + 'id', + 'sid', + 'key', + // 'metadata', Commented out because: https://discord.com/channels/509848480760725514/1093914405546229840/1095101536121667694 + 'mimeType', + 'userId', + 'hash', + 'project', + 'driver', + 'attribution', + 'licensing', + // 'tags', + 'url' + // 'stats' +]) +export const staticResourceQuerySchema = Type.Intersect( + [ + querySyntax(staticResourceQueryProperties), + // Add additional query properties here + Type.Object({}, { additionalProperties: false }) + ], + { additionalProperties: false } +) +export type StaticResourceQuery = Static diff --git a/packages/engine/src/schemas/user/avatar.schema.ts b/packages/engine/src/schemas/user/avatar.schema.ts new file mode 100644 index 00000000000..c564dad5fbd --- /dev/null +++ b/packages/engine/src/schemas/user/avatar.schema.ts @@ -0,0 +1,111 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +// For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html +import { querySyntax, Type } from '@feathersjs/typebox' +import type { Static } from '@feathersjs/typebox' + +import { staticResourceSchema } from '../media/static-resource.schema' + +export const avatarPath = 'avatar' + +export const avatarMethods = ['find', 'get', 'create', 'patch', 'remove'] as const + +// Main data model schema +export const avatarSchema = Type.Object( + { + id: Type.String({ + format: 'uuid' + }), + name: Type.String(), + identifierName: Type.String(), + modelResourceId: Type.String({ + format: 'uuid' + }), + thumbnailResourceId: Type.String({ + format: 'uuid' + }), + isPublic: Type.Boolean(), + userId: Type.String({ + format: 'uuid' + }), + project: Type.String(), + modelResource: Type.Ref(staticResourceSchema), + thumbnailResource: Type.Ref(staticResourceSchema), + createdAt: Type.String({ format: 'date-time' }), + updatedAt: Type.String({ format: 'date-time' }) + }, + { $id: 'Avatar', additionalProperties: false } +) +export type AvatarType = Static + +export type AvatarDatabaseType = Omit + +// Schema for creating new entries +// export const avatarDataSchema = Type.Pick( +// avatarSchema, +// ['name', 'identifierName', 'modelResourceId', 'thumbnailResourceId', 'isPublic', 'userId', 'project'], +// { +// $id: 'AvatarData' +// } +// ) +export const avatarDataSchema = Type.Partial(avatarSchema, { + $id: 'AvatarData' +}) +export type AvatarData = Static + +// Schema for updating existing entries +export const avatarPatchSchema = Type.Partial(avatarSchema, { + $id: 'AvatarPatch' +}) +export type AvatarPatch = Static + +// Schema for allowed query properties +export const avatarQueryProperties = Type.Pick(avatarSchema, [ + 'id', + 'name', + 'identifierName', + 'modelResourceId', + 'thumbnailResourceId', + 'isPublic', + 'userId', + 'project' +]) +export const avatarQuerySchema = Type.Intersect( + [ + querySyntax(avatarQueryProperties, { + name: { + $like: Type.String() + } + }), + // Add additional query properties here + Type.Object( + { admin: Type.Optional(Type.Boolean()), search: Type.Optional(Type.String()) }, + { additionalProperties: false } + ) + ], + { additionalProperties: false } +) +export type AvatarQuery = Static diff --git a/packages/server-core/src/hooks/verify-scope.test.ts b/packages/server-core/src/hooks/verify-scope.test.ts index 459acd834ca..48e6647277f 100755 --- a/packages/server-core/src/hooks/verify-scope.test.ts +++ b/packages/server-core/src/hooks/verify-scope.test.ts @@ -28,6 +28,7 @@ import assert from 'assert' import { UserInterface } from '@etherealengine/common/src/interfaces/User' import { destroyEngine } from '@etherealengine/engine/src/ecs/classes/Engine' +import { avatarPath } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { Application } from '../../declarations' import { createFeathersKoaApp } from '../createApp' @@ -59,7 +60,7 @@ describe('verify-scope', () => { const avatarName = `CyberbotGreen #${Math.random()}` const isGuest = true - const avatar = await app.service('avatar').create({ + const avatar = await app.service(avatarPath).create({ name: avatarName }) let user = (await app.service('user').create({ @@ -84,7 +85,7 @@ describe('verify-scope', () => { const avatarName = `CyberbotGreen #${Math.random()}` const isGuest = true - const avatar = await app.service('avatar').create({ + const avatar = await app.service(avatarPath).create({ name: avatarName }) @@ -115,7 +116,7 @@ describe('verify-scope', () => { const avatarName = `CyberbotGreen #${Math.random()}` const isGuest = false - const avatar = await app.service('avatar').create({ + const avatar = await app.service(avatarPath).create({ name: avatarName }) @@ -146,7 +147,7 @@ describe('verify-scope', () => { const avatarName = `CyberbotGreen #${Math.random()}` const isGuest = false - const avatar = await app.service('avatar').create({ + const avatar = await app.service(avatarPath).create({ name: avatarName }) diff --git a/packages/server-core/src/projects/project-permission/project-permission.test.ts b/packages/server-core/src/projects/project-permission/project-permission.test.ts index 08048a76544..4925477b302 100644 --- a/packages/server-core/src/projects/project-permission/project-permission.test.ts +++ b/packages/server-core/src/projects/project-permission/project-permission.test.ts @@ -31,6 +31,7 @@ import path from 'path' import { ProjectPermissionInterface } from '@etherealengine/common/src/interfaces/ProjectPermissionInterface' import { UserInterface } from '@etherealengine/common/src/interfaces/User' import { destroyEngine } from '@etherealengine/engine/src/ecs/classes/Engine' +import { avatarPath } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { Application } from '../../../declarations' import { createFeathersKoaApp } from '../../createApp' @@ -68,7 +69,7 @@ describe('project-permission.test', () => { await cleanup(app) const avatarName = 'CyberbotGreen' - const avatar = await app.service('avatar').create({ + const avatar = await app.service(avatarPath).create({ name: avatarName }) diff --git a/packages/server-core/src/projects/project/project.class.ts b/packages/server-core/src/projects/project/project.class.ts index 8d3f531c386..0cc4d431978 100644 --- a/packages/server-core/src/projects/project/project.class.ts +++ b/packages/server-core/src/projects/project/project.class.ts @@ -40,6 +40,7 @@ import { import { UserInterface } from '@etherealengine/common/src/interfaces/User' import { processFileName } from '@etherealengine/common/src/utils/processFileName' import { routePath, RouteType } from '@etherealengine/engine/src/schemas/route/route.schema' +import { avatarPath } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { getState } from '@etherealengine/hyperflux' import templateProjectJson from '@etherealengine/projects/template-project/package.json' @@ -596,19 +597,6 @@ export class Project extends Service { await this.app.service('location').remove(location.dataValues.id) }) - const whereClause = { - [Op.and]: [ - { - project: name - }, - { - project: { - [Op.ne]: null - } - } - ] - } - const routeItems = (await this.app.service(routePath).find({ query: { $and: [{ project: { $ne: null } }, { project: name }] @@ -621,17 +609,40 @@ export class Project extends Service { await this.app.service(routePath).remove(route.id) }) - const avatarItems = await (this.app.service('avatar') as any).Model.findAll({ - where: whereClause + const avatarItems = await this.app.service(avatarPath).find({ + query: { + $and: [ + { + project: name + }, + { + project: { + $ne: null + } + } + ] + } }) + await Promise.all( - avatarItems.map(async (avatar) => { - await this.app.service('avatar').remove(avatar.dataValues.id) + avatarItems.data.map(async (avatar) => { + await this.app.service(avatarPath).remove(avatar.id) }) ) const staticResourceItems = await (this.app.service('static-resource') as any).Model.findAll({ - where: whereClause + where: { + [Op.and]: [ + { + project: name + }, + { + project: { + [Op.ne]: null + } + } + ] + } }) staticResourceItems.length && staticResourceItems.forEach(async (staticResource) => { @@ -680,7 +691,7 @@ export class Project extends Service { include: [{ model: this.app.service('project').Model }], paginate: false })) as any - let allowedProjects = await projectPermissions.map((permission) => permission.project) + const allowedProjects = await projectPermissions.map((permission) => permission.project) const repoAccess = githubIdentityProvider ? await this.app.service('github-repo-access').Model.findAll({ paginate: false, diff --git a/packages/server-core/src/social/party-user/party-user.class.ts b/packages/server-core/src/social/party-user/party-user.class.ts index de6694c6371..0b8049ded4d 100755 --- a/packages/server-core/src/social/party-user/party-user.class.ts +++ b/packages/server-core/src/social/party-user/party-user.class.ts @@ -27,14 +27,13 @@ import { Params } from '@feathersjs/feathers/lib' import { SequelizeServiceOptions, Service } from 'feathers-sequelize' import { Op } from 'sequelize' -import { PartyUserInterface } from '@etherealengine/common/src/dbmodels/PartyUser' import { PartyUser as PartyUserDataType } from '@etherealengine/common/src/interfaces/PartyUser' import { UserInterface } from '@etherealengine/common/src/interfaces/User' +import { avatarPath } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { Application } from '../../../declarations' import logger from '../../ServerLogger' import { UserParams } from '../../user/user/user.class' -import { PartyUserModelStatic } from './party-user.model' interface PartyUserParams extends Params { deletingParty?: boolean @@ -83,7 +82,7 @@ export class PartyUser extends Service { users.map( (partyUser: PartyUserDataType) => new Promise(async (resolve, reject) => { - const avatar = await self.app.service('avatar').get(partyUser.user!.avatarId) + const avatar = await self.app.service(avatarPath).get(partyUser.user!.avatarId) if ((partyUser.user as any)!.dataValues) (partyUser.user as any)!.dataValues.avatar = avatar else partyUser.user!.avatar = avatar resolve(partyUser) diff --git a/packages/server-core/src/social/party-user/party-user.service.ts b/packages/server-core/src/social/party-user/party-user.service.ts index 9b63a4c31f7..dafe8c05ba3 100755 --- a/packages/server-core/src/social/party-user/party-user.service.ts +++ b/packages/server-core/src/social/party-user/party-user.service.ts @@ -30,6 +30,7 @@ import { getState } from '@etherealengine/hyperflux' import '@feathersjs/transport-commons' import { InstanceInterface } from '@etherealengine/common/src/dbmodels/Instance' +import { avatarPath } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { Application } from '../../../declarations' import logger from '../../ServerLogger' @@ -72,7 +73,7 @@ export default (app: Application): void => { data.user = await app.service('user').Model.findOne({ where: { id: data.userId } }) - const avatar = await app.service('avatar').get(data.user.avatarId) + const avatar = await app.service(avatarPath).get(data.user.avatarId) if (data.user.dataValues) data.user.dataValues.avatar = avatar else data.user.avatar = avatar return Promise.all( @@ -97,7 +98,7 @@ export default (app: Application): void => { data.user = await app.service('user').Model.findOne({ where: { id: data.userId } }) - const avatar = await app.service('avatar').get(data.user.avatarId) + const avatar = await app.service(avatarPath).get(data.user.avatarId) if (data.user.dataValues) data.user.dataValues.avatar = avatar else data.user.avatar = avatar return Promise.all( diff --git a/packages/server-core/src/social/party/party.test.ts b/packages/server-core/src/social/party/party.test.ts index 90609a459f8..c262a4ab3e5 100644 --- a/packages/server-core/src/social/party/party.test.ts +++ b/packages/server-core/src/social/party/party.test.ts @@ -30,6 +30,7 @@ import path from 'path' import { Party } from '@etherealengine/common/src/interfaces/Party' import { UserInterface } from '@etherealengine/common/src/interfaces/User' import { destroyEngine } from '@etherealengine/engine/src/ecs/classes/Engine' +import { avatarPath } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { Application } from '../../../declarations' import { createFeathersKoaApp } from '../../createApp' @@ -66,7 +67,7 @@ describe('party.test', () => { await app.setup() const avatarName = 'CyberbotGreen' - const avatar = await app.service('avatar').create({ + const avatar = await app.service(avatarPath).create({ name: avatarName }) diff --git a/packages/server-core/src/user/avatar/avatar-helper.ts b/packages/server-core/src/user/avatar/avatar-helper.ts index d600326f209..041d62b3b6a 100644 --- a/packages/server-core/src/user/avatar/avatar-helper.ts +++ b/packages/server-core/src/user/avatar/avatar-helper.ts @@ -28,32 +28,16 @@ import fs from 'fs' import path from 'path' import { CommonKnownContentTypes } from '@etherealengine/common/src/utils/CommonKnownContentTypes' +import { avatarPath, AvatarType } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { Application } from '../../../declarations' import { isAssetFromProject } from '../../media/static-resource/static-resource-helper' import { getStorageProvider } from '../../media/storageprovider/storageprovider' import { addAssetAsStaticResource } from '../../media/upload-asset/upload-asset.service' -import { getProjectPackageJson } from '../../projects/project/project-helper' import logger from '../../ServerLogger' import { getContentType } from '../../util/fileUtils' import { UserParams } from '../user/user.class' -export type AvatarCreateArguments = { - modelResourceId?: string - thumbnailResourceId?: string - identifierName?: string - name: string - isPublic?: boolean - project?: string -} - -export type AvatarPatchArguments = { - modelResourceId?: string - thumbnailResourceId?: string - identifierName?: string - name?: string -} - export type AvatarUploadArguments = { avatar: Buffer thumbnail: Buffer @@ -132,24 +116,32 @@ export const installAvatarsFromProject = async (app: Application, avatarsFolder: await Promise.all( avatarsToInstall.map(async (avatar) => { try { - const existingAvatar = await app.service('avatar').Model.findOne({ - where: { + const existingAvatar = await app.service(avatarPath).find({ + query: { name: avatar.avatarName, isPublic: true, - project: projectName || null + $or: [ + { + project: projectName + }, + { + project: '' + } + ] } }) console.log({ existingAvatar }) - let selectedAvatar - if (!existingAvatar) { - selectedAvatar = await app.service('avatar').create({ + + let selectedAvatar: AvatarType + if (existingAvatar && existingAvatar.data.length > 0) { + // todo - clean up old avatar files + selectedAvatar = existingAvatar.data[0] + } else { + selectedAvatar = await app.service(avatarPath).create({ name: avatar.avatarName, isPublic: true, - project: projectName || null! + project: projectName || undefined }) - } else { - // todo - clean up old avatar files - selectedAvatar = existingAvatar } await uploadDependencies(avatar.dependencies) @@ -221,11 +213,13 @@ export const uploadAvatarStaticResource = async ( if (data.avatarId) { try { - await app.service('avatar').patch(data.avatarId, { + await app.service(avatarPath).patch(data.avatarId, { modelResourceId: modelResource.id, thumbnailResourceId: thumbnailResource.id }) - } catch (err) {} + } catch (err) { + console.log(err) + } } return [modelResource, thumbnailResource] diff --git a/packages/server-core/src/user/avatar/avatar.class.ts b/packages/server-core/src/user/avatar/avatar.class.ts index 4f60c6190c2..6e592601ac8 100755 --- a/packages/server-core/src/user/avatar/avatar.class.ts +++ b/packages/server-core/src/user/avatar/avatar.class.ts @@ -23,68 +23,75 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { Paginated, Params } from '@feathersjs/feathers' -import { SequelizeServiceOptions, Service } from 'feathers-sequelize' -import { Op } from 'sequelize' - -import { AvatarInterface } from '@etherealengine/common/src/interfaces/AvatarInterface' +import { Id, Paginated, Params } from '@feathersjs/feathers' +import { KnexAdapter } from '@feathersjs/knex' +import type { KnexAdapterOptions, KnexAdapterParams } from '@feathersjs/knex' +import { Knex } from 'knex' + +import { UserInterface } from '@etherealengine/common/src/interfaces/User' +import { staticResourcePath, StaticResourceType } from '@etherealengine/engine/src/schemas/media/static-resource.schema' +import { + AvatarData, + AvatarDatabaseType, + AvatarPatch, + avatarPath, + AvatarQuery, + AvatarType +} from '@etherealengine/engine/src/schemas/user/avatar.schema' import { Application } from '../../../declarations' import { checkScope } from '../../hooks/verify-scope' import logger from '../../ServerLogger' -import { UnauthorizedException } from '../../util/exceptions/exception' -import { UserParams } from '../user/user.class' -import { AvatarCreateArguments, AvatarPatchArguments } from './avatar-helper' -export class Avatar extends Service { +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface AvatarParams extends KnexAdapterParams { + user?: UserInterface + isInternal?: boolean +} + +/** + * A class for Avatar service + */ + +export class AvatarService extends KnexAdapter< + AvatarType, + AvatarData, + AvatarParams, + AvatarPatch +> { app: Application - docs: any - constructor(options: Partial, app: Application) { + constructor(options: KnexAdapterOptions, app: Application) { super(options) this.app = app } - async get(id: string, params?: Params): Promise { - const avatar = await super.get(id, params) - if (avatar.modelResourceId) - try { - avatar.modelResource = await this.app.service('static-resource').get(avatar.modelResourceId) - } catch (err) { - logger.error(err) - } - if (avatar.thumbnailResourceId) - try { - avatar.thumbnailResource = await this.app.service('static-resource').get(avatar.thumbnailResourceId) - } catch (err) { - logger.error(err) - } - return avatar + async get(id: Id, params?: AvatarParams) { + return super._get(id, params) } - async find( - params?: UserParams & { query?: { admin?: boolean } } - ): Promise> { + async find(params?: AvatarParams) { let isAdmin = false + if (params && params.user && params.user.id && params.query?.admin) { - // todo do we want to use globalAvatars:read/write intead here? + // TODO: Do we want to use globalAvatars:read/write instead here? isAdmin = await checkScope(params?.user, this.app, 'admin', 'admin') delete params.query.admin } - if (params?.query?.search != null) { - if (params.query.search.length > 0) - params.query.name = { - [Op.like]: `%${params.query.search}%` - } + if (params?.query?.search) { + params.query.name = { + $like: `%${params.query.search}%` + } } - if (params?.query) delete params.query.search + + if (params?.query?.search) delete params.query.search if (!isAdmin && params) { if (params.user && params.user.id) { params.query = { ...params?.query, - [Op.or]: [ + $or: [ { isPublic: true }, @@ -102,81 +109,75 @@ export class Avatar extends Service { } } - const avatars = (await super.find(params)) as Paginated + const avatars = (await super._find(params)) as Paginated await Promise.all( avatars.data.map(async (avatar) => { if (avatar.modelResourceId) try { - avatar.modelResource = await this.app.service('static-resource').get(avatar.modelResourceId) - } catch (err) {} + //TODO: Remove `as StaticResourceType` once static-resource service is migrated to feathers 5. + avatar.modelResource = (await this.app + .service(staticResourcePath) + .get(avatar.modelResourceId)) as StaticResourceType + } catch (err) { + logger.error(err) + } if (avatar.thumbnailResourceId) try { - avatar.thumbnailResource = await this.app.service('static-resource').get(avatar.thumbnailResourceId) - } catch (err) {} + //TODO: Remove `as StaticResourceType` once static-resource service is migrated to feathers 5. + avatar.thumbnailResource = (await this.app + .service(staticResourcePath) + .get(avatar.thumbnailResourceId)) as StaticResourceType + } catch (err) { + logger.error(err) + } + return avatar }) ) + return avatars } - async create(data: AvatarCreateArguments, params?: UserParams): Promise { - let avatar = (await super.create({ - name: data.name, + async create(data: AvatarData, params?: AvatarParams) { + let avatar = await super._create({ + ...data, isPublic: data.isPublic ?? true, - userId: params?.user!.id, - modelResourceId: data.modelResourceId, - thumbnailResourceId: data.thumbnailResourceId, - project: data.project - })) as AvatarInterface - avatar = await this.patch(avatar.id, { + userId: params!.user!.id + }) + + avatar = await super._patch(avatar.id, { identifierName: avatar.name + '_' + avatar.id }) + return avatar } - async patch(id: string, data: AvatarPatchArguments, params?: UserParams): Promise { - let avatar = (await super.get(id, params)) as AvatarInterface - - if (avatar.userId !== params?.user!.id && params && params.user && params.user.id) { - const hasPermission = await checkScope(params?.user, this.app, 'admin', 'admin') - if (!hasPermission) { - throw new UnauthorizedException(`Unauthorized to perform this action.`) - } - } - - avatar = (await super.patch(id, data, params)) as AvatarInterface - avatar = (await super.patch(avatar.id, { - identifierName: avatar.name + '_' + avatar.id - })) as AvatarInterface - - if (avatar.modelResourceId) - try { - avatar.modelResource = await this.app.service('static-resource').get(avatar.modelResourceId) - } catch (err) {} - if (avatar.thumbnailResourceId) - try { - avatar.thumbnailResource = await this.app.service('static-resource').get(avatar.thumbnailResourceId) - } catch (err) {} - return avatar + async patch(id: Id, data: AvatarPatch, params?: AvatarParams) { + return await super._patch(id, data, params) } - async remove(id: string, params?: Params): Promise { + async remove(id: string, params?: AvatarParams) { const avatar = await this.get(id, params) + try { - await this.app.service('static-resource').remove(avatar.modelResourceId) + await this.app.service(staticResourcePath).remove(avatar.modelResourceId) } catch (err) { logger.error(err) } + try { - await this.app.service('static-resource').remove(avatar.thumbnailResourceId) - } catch (err) {} - const avatars = (await super.Model.findAll({ - where: { - id: { - [Op.ne]: id - } - } - })) as AvatarInterface[] + await this.app.service(staticResourcePath).remove(avatar.thumbnailResourceId) + } catch (err) { + logger.error(err) + } + + const knexClient: Knex = this.app.get('knexClient') + + const avatars = (await knexClient + .from(avatarPath) + .whereNot('id', id) + .select()) as AvatarDatabaseType[] + //Users that have the avatar that's being deleted will have theirs replaced with a random one, if there are other //avatars to use if (id && avatars.length > 0) { @@ -192,6 +193,7 @@ export class Avatar extends Service { } ) } - return super.remove(id, params) as Promise + + return super._remove(id, params) } } diff --git a/packages/server-core/src/user/avatar/avatar.docs.ts b/packages/server-core/src/user/avatar/avatar.docs.ts index d10a3f490c3..18815c934c7 100755 --- a/packages/server-core/src/user/avatar/avatar.docs.ts +++ b/packages/server-core/src/user/avatar/avatar.docs.ts @@ -23,22 +23,24 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -/** - * An object for swagger documentation configuration - */ -export default { - definitions: { - avatar: { - type: 'object', - properties: { - name: { - type: 'string' - } - } - }, - avatar_list: { - type: 'array', - items: { $ref: '#/definitions/avatar' } - } +import { createSwaggerServiceOptions } from 'feathers-swagger' + +import { + avatarDataSchema, + avatarPatchSchema, + avatarQuerySchema, + avatarSchema +} from '@etherealengine/engine/src/schemas/user/avatar.schema' + +export default createSwaggerServiceOptions({ + schemas: { + avatarDataSchema, + avatarPatchSchema, + avatarQuerySchema, + avatarSchema + }, + docs: { + description: 'Avatar service description', + securities: ['all'] } -} +}) diff --git a/packages/server-core/src/user/avatar/avatar.hooks.ts b/packages/server-core/src/user/avatar/avatar.hooks.ts index 779796c51a6..a24de457508 100755 --- a/packages/server-core/src/user/avatar/avatar.hooks.ts +++ b/packages/server-core/src/user/avatar/avatar.hooks.ts @@ -23,21 +23,55 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ +import { hooks as schemaHooks } from '@feathersjs/schema' +import { getValidator } from '@feathersjs/typebox' import { disallow, iff, isProvider } from 'feathers-hooks-common' -import addAssociations from '../../hooks/add-associations' +import { + avatarDataSchema, + avatarPatchSchema, + avatarQuerySchema, + avatarSchema +} from '@etherealengine/engine/src/schemas/user/avatar.schema' +import { dataValidator, queryValidator } from '@etherealengine/server-core/validators' + import authenticate from '../../hooks/authenticate' import verifyScope from '../../hooks/verify-scope' +import { + avatarDataResolver, + avatarExternalResolver, + avatarPatchResolver, + avatarQueryResolver, + avatarResolver +} from './avatar.resolvers' + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const avatarValidator = getValidator(avatarSchema, dataValidator) +const avatarDataValidator = getValidator(avatarDataSchema, dataValidator) +const avatarPatchValidator = getValidator(avatarPatchSchema, dataValidator) +const avatarQueryValidator = getValidator(avatarQuerySchema, queryValidator) export default { + around: { + all: [schemaHooks.resolveExternal(avatarExternalResolver), schemaHooks.resolveResult(avatarResolver)] + }, + before: { - all: [authenticate()], + all: [ + authenticate(), + () => schemaHooks.validateQuery(avatarQueryValidator), + schemaHooks.resolveQuery(avatarQueryResolver) + ], find: [], get: [], - create: [], + create: [() => schemaHooks.validateData(avatarDataValidator), schemaHooks.resolveData(avatarDataResolver)], update: [disallow()], - patch: [], - remove: [iff(isProvider('external'), verifyScope('admin', 'admin') as any)] + patch: [ + iff(isProvider('external'), verifyScope('admin', 'admin')), + () => schemaHooks.validateData(avatarPatchValidator), + schemaHooks.resolveData(avatarPatchResolver) + ], + remove: [iff(isProvider('external'), verifyScope('admin', 'admin'))] }, after: { all: [], diff --git a/packages/server-core/src/user/avatar/avatar.model.ts b/packages/server-core/src/user/avatar/avatar.model.ts deleted file mode 100755 index 119ff4c700e..00000000000 --- a/packages/server-core/src/user/avatar/avatar.model.ts +++ /dev/null @@ -1,83 +0,0 @@ -/* -CPAL-1.0 License - -The contents of this file are subject to the Common Public Attribution License -Version 1.0. (the "License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at -https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. -The License is based on the Mozilla Public License Version 1.1, but Sections 14 -and 15 have been added to cover use of software over a computer network and -provide for limited attribution for the Original Developer. In addition, -Exhibit A has been modified to be consistent with Exhibit B. - -Software distributed under the License is distributed on an "AS IS" basis, -WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the -specific language governing rights and limitations under the License. - -The Original Code is Ethereal Engine. - -The Original Developer is the Initial Developer. The Initial Developer of the -Original Code is the Ethereal Engine team. - -All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 -Ethereal Engine. All Rights Reserved. -*/ - -import { DataTypes, Model, Sequelize } from 'sequelize' - -import { AvatarInterface } from '@etherealengine/common/src/dbmodels/AvatarResource' - -import { Application } from '../../../declarations' - -export default (app: Application) => { - const sequelizeClient: Sequelize = app.get('sequelizeClient') - const Avatar = sequelizeClient.define>( - 'avatar', - { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV1, - allowNull: false, - primaryKey: true - }, - name: { - type: DataTypes.STRING, - allowNull: true - }, - identifierName: { - type: DataTypes.STRING, - unique: true - }, - modelResourceId: { - type: DataTypes.STRING - }, - thumbnailResourceId: { - type: DataTypes.STRING - }, - isPublic: { - type: DataTypes.BOOLEAN, - defaultValue: true - }, - userId: { - type: DataTypes.UUID - }, - project: { - type: DataTypes.STRING, - allowNull: true - } - }, - { - hooks: { - beforeCount(options: any): void { - options.raw = true - } - } - } - ) - - ;(Avatar as any).associate = (models: any): void => { - ;(Avatar as any).hasMany(models.user) - } - - return Avatar -} diff --git a/packages/server-core/src/user/avatar/avatar.resolvers.ts b/packages/server-core/src/user/avatar/avatar.resolvers.ts new file mode 100644 index 00000000000..ee9a45fba6c --- /dev/null +++ b/packages/server-core/src/user/avatar/avatar.resolvers.ts @@ -0,0 +1,67 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +// For more information about this file see https://dove.feathersjs.com/guides/cli/service.schemas.html +import { resolve } from '@feathersjs/schema' +import { v4 } from 'uuid' + +import { staticResourcePath, StaticResourceType } from '@etherealengine/engine/src/schemas/media/static-resource.schema' +import { AvatarDatabaseType, AvatarQuery, AvatarType } from '@etherealengine/engine/src/schemas/user/avatar.schema' +import type { HookContext } from '@etherealengine/server-core/declarations' + +import { getDateTimeSql } from '../../util/get-datetime-sql' + +export const avatarResolver = resolve({ + modelResource: async (value, avatar, context) => { + //TODO: We should replace `as any as StaticResourceType` with `as StaticResourceType` once static-resource service is migrated to feathers 5. + const modelStaticResource = (await context.app + .service(staticResourcePath) + .get(avatar.modelResourceId)) as any as StaticResourceType + return modelStaticResource + }, + thumbnailResource: async (value, avatar, context) => { + //TODO: We should replace `as any as StaticResourceType` with `as StaticResourceType` once static-resource service is migrated to feathers 5. + const thumbnailStaticResource = (await context.app + .service(staticResourcePath) + .get(avatar.thumbnailResourceId)) as any as StaticResourceType + return thumbnailStaticResource + } +}) + +export const avatarExternalResolver = resolve({}) + +export const avatarDataResolver = resolve({ + id: async () => { + return v4() + }, + createdAt: getDateTimeSql, + updatedAt: getDateTimeSql +}) + +export const avatarPatchResolver = resolve({ + updatedAt: getDateTimeSql +}) + +export const avatarQueryResolver = resolve({}) diff --git a/packages/server-core/src/user/avatar/avatar.service.ts b/packages/server-core/src/user/avatar/avatar.ts similarity index 73% rename from packages/server-core/src/user/avatar/avatar.service.ts rename to packages/server-core/src/user/avatar/avatar.ts index 3c680db5076..5173e9c8878 100755 --- a/packages/server-core/src/user/avatar/avatar.service.ts +++ b/packages/server-core/src/user/avatar/avatar.ts @@ -23,34 +23,35 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ +import { avatarMethods, avatarPath } from '@etherealengine/engine/src/schemas/user/avatar.schema' + import { Application } from '../../../declarations' -import { Avatar } from './avatar.class' +import { AvatarService } from './avatar.class' import avatarDocs from './avatar.docs' import hooks from './avatar.hooks' -import createModel from './avatar.model' declare module '@etherealengine/common/declarations' { - /** - * Interface for users input - */ interface ServiceTypes { - avatar: Avatar + [avatarPath]: AvatarService } } export default (app: Application): void => { const options = { - Model: createModel(app), + name: avatarPath, paginate: app.get('paginate'), + Model: app.get('knexClient'), multi: true } - const event = new Avatar(options, app) - event.docs = avatarDocs - - app.use('avatar', event) - - const service = app.service('avatar') + app.use(avatarPath, new AvatarService(options, app), { + // A list of all methods this service exposes externally + methods: avatarMethods, + // You can add additional custom events to be sent to clients here + events: [], + docs: avatarDocs + }) + const service = app.service(avatarPath) service.hooks(hooks) } diff --git a/packages/server-core/src/user/identity-provider/identity-provider.class.ts b/packages/server-core/src/user/identity-provider/identity-provider.class.ts index 8f19683902e..4abffa9b9b2 100755 --- a/packages/server-core/src/user/identity-provider/identity-provider.class.ts +++ b/packages/server-core/src/user/identity-provider/identity-provider.class.ts @@ -31,8 +31,8 @@ import { v1 as uuidv1 } from 'uuid' import { isDev } from '@etherealengine/common/src/config' import { IdentityProviderInterface } from '@etherealengine/common/src/dbmodels/IdentityProvider' -import { AvatarInterface } from '@etherealengine/common/src/interfaces/AvatarInterface' import { UserInterface } from '@etherealengine/common/src/interfaces/User' +import { avatarPath } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { Application } from '../../../declarations' import appConfig from '../../appconfig' @@ -198,9 +198,7 @@ export class IdentityProvider extends Service } ] }) - const avatars = (await this.app - .service('avatar') - .find({ isInternal: true, query: { $limit: 1000 } })) as Paginated + const avatars = await this.app.service(avatarPath).find({ isInternal: true, query: { $limit: 1000 } }) let isGuest = type === 'guest' diff --git a/packages/server-core/src/user/services.ts b/packages/server-core/src/user/services.ts index c8a2a4c90eb..09a96001dce 100755 --- a/packages/server-core/src/user/services.ts +++ b/packages/server-core/src/user/services.ts @@ -25,7 +25,7 @@ Ethereal Engine. All Rights Reserved. import AcceptInvite from '../user/accept-invite/accept-invite.service' import Auth from './auth-management/auth-management.service' -import Avatar from './avatar/avatar.service' +import Avatar from './avatar/avatar' import DiscordBotAuth from './discord-bot-auth/discord-bot-auth.service' import Email from './email/email.service' import GithubRepoAccess from './github-repo-access/github-repo-access.service' diff --git a/packages/server-core/src/user/strategies/discord.ts b/packages/server-core/src/user/strategies/discord.ts index 81fce2bc261..2db691d4553 100755 --- a/packages/server-core/src/user/strategies/discord.ts +++ b/packages/server-core/src/user/strategies/discord.ts @@ -24,11 +24,11 @@ Ethereal Engine. All Rights Reserved. */ import { AuthenticationRequest } from '@feathersjs/authentication' -import { Paginated, Params } from '@feathersjs/feathers' +import { Params } from '@feathersjs/feathers' import { random } from 'lodash' -import { AvatarInterface } from '@etherealengine/common/src/interfaces/AvatarInterface' import { UserInterface } from '@etherealengine/common/src/interfaces/User' +import { avatarPath } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { Application } from '../../../declarations' import config from '../../appconfig' @@ -66,7 +66,7 @@ export class DiscordStrategy extends CustomOAuthStrategy { {} ) if (!entity.userId) { - const avatars = (await this.app.service('avatar').find({ isInternal: true })) as Paginated + const avatars = await this.app.service(avatarPath).find({ isInternal: true }) const code = await getFreeInviteCode(this.app) const newUser = (await this.app.service('user').create({ isGuest: false, diff --git a/packages/server-core/src/user/strategies/facebook.ts b/packages/server-core/src/user/strategies/facebook.ts index 6f57bc885b3..07ddc1b3240 100755 --- a/packages/server-core/src/user/strategies/facebook.ts +++ b/packages/server-core/src/user/strategies/facebook.ts @@ -24,11 +24,11 @@ Ethereal Engine. All Rights Reserved. */ import { AuthenticationRequest } from '@feathersjs/authentication' -import { Paginated, Params } from '@feathersjs/feathers' +import { Params } from '@feathersjs/feathers' import { random } from 'lodash' -import { AvatarInterface } from '@etherealengine/common/src/interfaces/AvatarInterface' import { UserInterface } from '@etherealengine/common/src/interfaces/User' +import { avatarPath } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { Application } from '../../../declarations' import config from '../../appconfig' @@ -66,7 +66,7 @@ export class FacebookStrategy extends CustomOAuthStrategy { {} ) if (!entity.userId) { - const avatars = (await this.app.service('avatar').find({ isInternal: true })) as Paginated + const avatars = await this.app.service(avatarPath).find({ isInternal: true }) const code = await getFreeInviteCode(this.app) const newUser = (await this.app.service('user').create({ isGuest: false, diff --git a/packages/server-core/src/user/strategies/github.ts b/packages/server-core/src/user/strategies/github.ts index a7b9e936ec2..c77af17801f 100755 --- a/packages/server-core/src/user/strategies/github.ts +++ b/packages/server-core/src/user/strategies/github.ts @@ -24,11 +24,10 @@ Ethereal Engine. All Rights Reserved. */ import { AuthenticationRequest } from '@feathersjs/authentication' -import { Paginated, Params } from '@feathersjs/feathers' import { random } from 'lodash' -import { AvatarInterface } from '@etherealengine/common/src/interfaces/AvatarInterface' import { UserInterface } from '@etherealengine/common/src/interfaces/User' +import { avatarPath } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { Application } from '../../../declarations' import config from '../../appconfig' @@ -68,7 +67,7 @@ export class GithubStrategy extends CustomOAuthStrategy { {} ) if (!entity.userId) { - const avatars = (await this.app.service('avatar').find({ isInternal: true })) as Paginated + const avatars = await this.app.service(avatarPath).find({ isInternal: true }) const code = await getFreeInviteCode(this.app) const newUser = (await this.app.service('user').create({ isGuest: false, diff --git a/packages/server-core/src/user/strategies/google.ts b/packages/server-core/src/user/strategies/google.ts index 52976c453c0..946b078e7c6 100755 --- a/packages/server-core/src/user/strategies/google.ts +++ b/packages/server-core/src/user/strategies/google.ts @@ -27,8 +27,8 @@ import { AuthenticationRequest } from '@feathersjs/authentication' import { Paginated, Params } from '@feathersjs/feathers' import { random } from 'lodash' -import { AvatarInterface } from '@etherealengine/common/src/interfaces/AvatarInterface' import { UserInterface } from '@etherealengine/common/src/interfaces/User' +import { avatarPath } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { Application } from '../../../declarations' import config from '../../appconfig' @@ -66,7 +66,7 @@ export class Googlestrategy extends CustomOAuthStrategy { {} ) if (!entity.userId) { - const avatars = (await this.app.service('avatar').find({ isInternal: true })) as Paginated + const avatars = await this.app.service(avatarPath).find({ isInternal: true }) const code = await getFreeInviteCode(this.app) const newUser = (await this.app.service('user').create({ isGuest: false, diff --git a/packages/server-core/src/user/strategies/linkedin.ts b/packages/server-core/src/user/strategies/linkedin.ts index faf64fac3d4..1eb7db0919e 100755 --- a/packages/server-core/src/user/strategies/linkedin.ts +++ b/packages/server-core/src/user/strategies/linkedin.ts @@ -24,11 +24,11 @@ Ethereal Engine. All Rights Reserved. */ import { AuthenticationRequest } from '@feathersjs/authentication' -import { Paginated, Params } from '@feathersjs/feathers' +import { Params } from '@feathersjs/feathers' import { random } from 'lodash' -import { AvatarInterface } from '@etherealengine/common/src/interfaces/AvatarInterface' import { UserInterface } from '@etherealengine/common/src/interfaces/User' +import { avatarPath } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { Application } from '../../../declarations' import config from '../../appconfig' @@ -66,7 +66,7 @@ export class LinkedInStrategy extends CustomOAuthStrategy { {} ) if (!entity.userId) { - const avatars = (await this.app.service('avatar').find({ isInternal: true })) as Paginated + const avatars = await this.app.service(avatarPath).find({ isInternal: true }) const code = await getFreeInviteCode(this.app) const newUser = (await this.app.service('user').create({ isGuest: false, diff --git a/packages/server-core/src/user/strategies/twitter.ts b/packages/server-core/src/user/strategies/twitter.ts index a33938d75ef..7b15956daf5 100755 --- a/packages/server-core/src/user/strategies/twitter.ts +++ b/packages/server-core/src/user/strategies/twitter.ts @@ -24,11 +24,11 @@ Ethereal Engine. All Rights Reserved. */ import { AuthenticationRequest } from '@feathersjs/authentication' -import { Paginated, Params } from '@feathersjs/feathers' +import { Params } from '@feathersjs/feathers' import { random } from 'lodash' -import { AvatarInterface } from '@etherealengine/common/src/interfaces/AvatarInterface' import { UserInterface } from '@etherealengine/common/src/interfaces/User' +import { avatarPath } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { Application } from '../../../declarations' import config from '../../appconfig' @@ -67,7 +67,7 @@ export class TwitterStrategy extends CustomOAuthStrategy { {} ) if (!entity.userId) { - const avatars = (await this.app.service('avatar').find({ isInternal: true })) as Paginated + const avatars = await this.app.service(avatarPath).find({ isInternal: true }) const code = await getFreeInviteCode(this.app) const newUser = (await this.app.service('user').create({ isGuest: false, diff --git a/packages/server-core/src/user/user/user.model.ts b/packages/server-core/src/user/user/user.model.ts index eacfdab0936..7a19ccada34 100755 --- a/packages/server-core/src/user/user/user.model.ts +++ b/packages/server-core/src/user/user/user.model.ts @@ -25,7 +25,7 @@ Ethereal Engine. All Rights Reserved. import { DataTypes, Model, Sequelize } from 'sequelize' -import { UserInterface } from '@etherealengine/common/src/dbmodels/UserInterface' +import { AvatarInterface, UserInterface } from '@etherealengine/common/src/dbmodels/UserInterface' import { Application } from '../../../declarations' @@ -96,9 +96,62 @@ export default (app: Application) => { ;(User as any).belongsToMany(models.instance, { through: 'instance_authorized_user' }) ;(User as any).hasMany(models.instance_authorized_user, { foreignKey: { allowNull: false } }) ;(User as any).hasOne(models.user_api_key) - ;(User as any).belongsTo(models.avatar) + ;(User as any).belongsTo(createAvatarModel(app)) ;(User as any).hasMany(models.user_kick, { onDelete: 'cascade' }) } return User } + +const createAvatarModel = (app: Application) => { + const sequelizeClient: Sequelize = app.get('sequelizeClient') + const Avatar = sequelizeClient.define>( + 'avatar', + { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV1, + allowNull: false, + primaryKey: true + }, + name: { + type: DataTypes.STRING, + allowNull: true + }, + identifierName: { + type: DataTypes.STRING, + unique: true + }, + modelResourceId: { + type: DataTypes.STRING + }, + thumbnailResourceId: { + type: DataTypes.STRING + }, + isPublic: { + type: DataTypes.BOOLEAN, + defaultValue: true + }, + userId: { + type: DataTypes.UUID + }, + project: { + type: DataTypes.STRING, + allowNull: true + } + }, + { + hooks: { + beforeCount(options: any): void { + options.raw = true + } + } + } + ) + + ;(Avatar as any).associate = (models: any): void => { + ;(Avatar as any).hasMany(models.user) + } + + return Avatar +} diff --git a/packages/server-core/src/user/user/user.test.ts b/packages/server-core/src/user/user/user.test.ts index 04f979eecf1..2a23c941e93 100755 --- a/packages/server-core/src/user/user/user.test.ts +++ b/packages/server-core/src/user/user/user.test.ts @@ -28,6 +28,7 @@ import { v1 } from 'uuid' import { UserInterface } from '@etherealengine/common/src/interfaces/User' import { destroyEngine } from '@etherealengine/engine/src/ecs/classes/Engine' +import { avatarPath } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { Application } from '../../../declarations' import { createFeathersKoaApp } from '../../createApp' @@ -54,7 +55,7 @@ describe('user service', () => { const avatarName = 'CyberbotGreen' const isGuest = true - const avatar = await app.service('avatar').create({ + const avatar = await app.service(avatarPath).create({ name: avatarName }) @@ -76,7 +77,7 @@ describe('user service', () => { const avatarName = 'CyberbotGreen' const isGuest = false - const avatar = await app.service('avatar').create({ + const avatar = await app.service(avatarPath).create({ name: avatarName }) diff --git a/scripts/create-build-status.ts b/scripts/create-build-status.ts index c4490fca53e..e5e67a509e7 100644 --- a/scripts/create-build-status.ts +++ b/scripts/create-build-status.ts @@ -30,10 +30,7 @@ import dotenv from 'dotenv-flow' import fs from 'fs' import knex from 'knex' -import { - BuildStatusType, - buildStatusPath -} from '@etherealengine/engine/src/schemas/cluster/build-status.schema' +import { buildStatusPath, BuildStatusType } from '@etherealengine/engine/src/schemas/cluster/build-status.schema' dotenv.config({ path: appRootPath.path, diff --git a/scripts/fetch-helm-versions.ts b/scripts/fetch-helm-versions.ts index 1f6b73ad0a0..c8f19139a0f 100644 --- a/scripts/fetch-helm-versions.ts +++ b/scripts/fetch-helm-versions.ts @@ -32,75 +32,68 @@ import knex from 'knex' import path from 'path' import { promisify } from 'util' +import { helmSettingPath, HelmSettingType } from '@etherealengine/engine/src/schemas/setting/helm-setting.schema' import { - helmSettingPath, - HelmSettingType -} from '@etherealengine/engine/src/schemas/setting/helm-setting.schema' -import { - MAIN_CHART_REGEX, - BUILDER_CHART_REGEX + BUILDER_CHART_REGEX, + MAIN_CHART_REGEX } from '@etherealengine/server-core/src/setting/helm-setting/helm-setting' dotenv.config({ - path: appRootPath.path, - silent: true + path: appRootPath.path, + silent: true }) const execAsync = promisify(exec) cli.enable('status') - const options = cli.parse({ - stage: [true, 'dev, prod, etc; deployment stage', 'string'] + stage: [true, 'dev, prod, etc; deployment stage', 'string'] }) cli.main(async () => { - try { - const knexClient = knex({ - client: 'mysql', - connection: { - user: process.env.MYSQL_USER ?? 'server', - password: process.env.MYSQL_PASSWORD ?? 'password', - host: process.env.MYSQL_HOST ?? '127.0.0.1', - port: parseInt(process.env.MYSQL_PORT || '3306'), - database: process.env.MYSQL_DATABASE ?? 'etherealengine', - charset: 'utf8mb4' - } - }) - - const [helmSettings] = await knexClient - .select() - .from(helmSettingPath) - - const helmMainVersionName = path.join(appRootPath.path, 'helm-main-version.txt') - const helmBuilderVersionName = path.join(appRootPath.path, 'helm-builder-version.txt') - - if (helmSettings) { - if (helmSettings.main && helmSettings.main.length > 0) - fs.writeFileSync(helmMainVersionName, helmSettings.main) - else { - const { stdout } = await execAsync(`helm history ${options.stage} | grep deployed`) - const mainChartVersion = MAIN_CHART_REGEX.exec(stdout) - if (mainChartVersion) fs.writeFileSync(helmMainVersionName, mainChartVersion[1]) - } - if (helmSettings.builder && helmSettings.builder.length > 0) - fs.writeFileSync(helmBuilderVersionName, helmSettings.builder) - else { - const { stdout } = await execAsync(`helm history ${options.stage}-builder | grep deployed`) - const builderChartVersion = BUILDER_CHART_REGEX.exec(stdout) - if (builderChartVersion) fs.writeFileSync(helmBuilderVersionName, builderChartVersion[1]) - } - } else { - const { stdout } = await execAsync(`helm history ${options.stage} | grep deployed`) - const mainChartVersion = MAIN_CHART_REGEX.exec(stdout) - if (mainChartVersion) fs.writeFileSync(helmMainVersionName, mainChartVersion[1]) - const builderChartVersion = BUILDER_CHART_REGEX.exec(stdout) - if (builderChartVersion) fs.writeFileSync(helmBuilderVersionName, builderChartVersion[1]) - } - cli.exit(0) - } catch (err) { - console.log(err) - cli.fatal(err) + try { + const knexClient = knex({ + client: 'mysql', + connection: { + user: process.env.MYSQL_USER ?? 'server', + password: process.env.MYSQL_PASSWORD ?? 'password', + host: process.env.MYSQL_HOST ?? '127.0.0.1', + port: parseInt(process.env.MYSQL_PORT || '3306'), + database: process.env.MYSQL_DATABASE ?? 'etherealengine', + charset: 'utf8mb4' + } + }) + + const [helmSettings] = await knexClient.select().from(helmSettingPath) + + const helmMainVersionName = path.join(appRootPath.path, 'helm-main-version.txt') + const helmBuilderVersionName = path.join(appRootPath.path, 'helm-builder-version.txt') + + if (helmSettings) { + if (helmSettings.main && helmSettings.main.length > 0) fs.writeFileSync(helmMainVersionName, helmSettings.main) + else { + const { stdout } = await execAsync(`helm history ${options.stage} | grep deployed`) + const mainChartVersion = MAIN_CHART_REGEX.exec(stdout) + if (mainChartVersion) fs.writeFileSync(helmMainVersionName, mainChartVersion[1]) + } + if (helmSettings.builder && helmSettings.builder.length > 0) + fs.writeFileSync(helmBuilderVersionName, helmSettings.builder) + else { + const { stdout } = await execAsync(`helm history ${options.stage}-builder | grep deployed`) + const builderChartVersion = BUILDER_CHART_REGEX.exec(stdout) + if (builderChartVersion) fs.writeFileSync(helmBuilderVersionName, builderChartVersion[1]) + } + } else { + const { stdout } = await execAsync(`helm history ${options.stage} | grep deployed`) + const mainChartVersion = MAIN_CHART_REGEX.exec(stdout) + if (mainChartVersion) fs.writeFileSync(helmMainVersionName, mainChartVersion[1]) + const builderChartVersion = BUILDER_CHART_REGEX.exec(stdout) + if (builderChartVersion) fs.writeFileSync(helmBuilderVersionName, builderChartVersion[1]) } + cli.exit(0) + } catch (err) { + console.log(err) + cli.fatal(err) + } }) diff --git a/scripts/record-build-error.ts b/scripts/record-build-error.ts index 6555b4caca8..06f5ae42558 100644 --- a/scripts/record-build-error.ts +++ b/scripts/record-build-error.ts @@ -29,10 +29,7 @@ import dotenv from 'dotenv-flow' import fs from 'fs' import knex from 'knex' -import { - BuildStatusType, - buildStatusPath -} from '@etherealengine/engine/src/schemas/cluster/build-status.schema' +import { buildStatusPath, BuildStatusType } from '@etherealengine/engine/src/schemas/cluster/build-status.schema' dotenv.config({ path: appRootPath.path, diff --git a/scripts/record-build-success.ts b/scripts/record-build-success.ts index 3269b7c4079..555ca022691 100644 --- a/scripts/record-build-success.ts +++ b/scripts/record-build-success.ts @@ -29,10 +29,7 @@ import dotenv from 'dotenv-flow' import fs from 'fs' import knex from 'knex' -import { - BuildStatusType, - buildStatusPath -} from '@etherealengine/engine/src/schemas/cluster/build-status.schema' +import { buildStatusPath, BuildStatusType } from '@etherealengine/engine/src/schemas/cluster/build-status.schema' dotenv.config({ path: appRootPath.path, From bf25d8f9d8ae60d6f3dedcc6e5e35d7c32c53e18 Mon Sep 17 00:00:00 2001 From: hanzlamateen Date: Tue, 11 Jul 2023 22:49:33 +0500 Subject: [PATCH 2/7] Fixed error due to static resource schema --- .../static-resource/static-resource.hooks.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/server-core/src/media/static-resource/static-resource.hooks.ts b/packages/server-core/src/media/static-resource/static-resource.hooks.ts index 5a711d3107f..8d56fbe23c5 100755 --- a/packages/server-core/src/media/static-resource/static-resource.hooks.ts +++ b/packages/server-core/src/media/static-resource/static-resource.hooks.ts @@ -23,15 +23,32 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ +import { getValidator } from '@feathersjs/typebox' import { disallow, iff, isProvider } from 'feathers-hooks-common' +import { + staticResourceDataSchema, + staticResourcePatchSchema, + staticResourceQuerySchema, + staticResourceSchema +} from '@etherealengine/engine/src/schemas/media/static-resource.schema' import collectAnalytics from '@etherealengine/server-core/src/hooks/collect-analytics' import attachOwnerIdInQuery from '@etherealengine/server-core/src/hooks/set-loggedin-user-in-query' +import { dataValidator, queryValidator } from '@etherealengine/server-core/validators' import addAssociations from '../../hooks/add-associations' import authenticate from '../../hooks/authenticate' import verifyScope from '../../hooks/verify-scope' +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const staticResourceValidator = getValidator(staticResourceSchema, dataValidator) +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const staticResourceDataValidator = getValidator(staticResourceDataSchema, dataValidator) +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const staticResourcePatchValidator = getValidator(staticResourcePatchSchema, dataValidator) +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const staticResourceQueryValidator = getValidator(staticResourceQuerySchema, queryValidator) + export default { before: { all: [], From 15cd75394621f3dfd95033d0a6f397928404e44e Mon Sep 17 00:00:00 2001 From: hanzlamateen Date: Tue, 11 Jul 2023 23:37:41 +0500 Subject: [PATCH 3/7] Fixed issue with missing associations --- .../server-core/src/hooks/add-associations.ts | 15 ++++++++++++++- packages/server-core/src/user/user/user.model.ts | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/server-core/src/hooks/add-associations.ts b/packages/server-core/src/hooks/add-associations.ts index 3300942eb29..68481a8fa46 100755 --- a/packages/server-core/src/hooks/add-associations.ts +++ b/packages/server-core/src/hooks/add-associations.ts @@ -28,6 +28,13 @@ import { Hook, HookContext } from '@feathersjs/feathers' import { Application } from '@etherealengine/server-core/declarations' import logger from '../ServerLogger' +import { createAvatarModel } from '../user/user/user.model' + +const getMigratedModels = (app: Application) => { + return { + avatar: createAvatarModel(app) + } +} function processInclude(context: HookContext, includeCollection?: ModelType[]) { if (!includeCollection) { @@ -56,13 +63,19 @@ type ModelAssociationsType = { export default (options: ModelAssociationsType): Hook => { return (context: HookContext): HookContext => { if (!context.params) context.params = {} + try { const sequelize = context.params.sequelize || {} const include: ModelType[] = sequelize.include || [] sequelize.include = include.concat( options.models.map((model: ModelType) => { const newModel = { ...model, ...processInclude(context, model.include) } as ModelType - newModel.model = context.app.services[model.model].Model + if (context.app.services[model.model].Model.name !== 'knex') { + newModel.model = context.app.services[model.model].Model + } else { + const migratedModels = getMigratedModels(context.app) + newModel.model = migratedModels[model.model] + } return newModel }) ) diff --git a/packages/server-core/src/user/user/user.model.ts b/packages/server-core/src/user/user/user.model.ts index 7a19ccada34..6c4c2fe4d96 100755 --- a/packages/server-core/src/user/user/user.model.ts +++ b/packages/server-core/src/user/user/user.model.ts @@ -103,7 +103,7 @@ export default (app: Application) => { return User } -const createAvatarModel = (app: Application) => { +export const createAvatarModel = (app: Application) => { const sequelizeClient: Sequelize = app.get('sequelizeClient') const Avatar = sequelizeClient.define>( 'avatar', From 0ec3e02f0a371a884b5340f1bd957ae6058b0148 Mon Sep 17 00:00:00 2001 From: hanzlamateen Date: Tue, 11 Jul 2023 23:38:51 +0500 Subject: [PATCH 4/7] Removed static resource fetching as its already done in resolver --- .../src/user/avatar/avatar.class.ts | 34 +++---------------- 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/packages/server-core/src/user/avatar/avatar.class.ts b/packages/server-core/src/user/avatar/avatar.class.ts index 6e592601ac8..1aa2612ae06 100755 --- a/packages/server-core/src/user/avatar/avatar.class.ts +++ b/packages/server-core/src/user/avatar/avatar.class.ts @@ -23,13 +23,13 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { Id, Paginated, Params } from '@feathersjs/feathers' +import { Id, Params } from '@feathersjs/feathers' import { KnexAdapter } from '@feathersjs/knex' import type { KnexAdapterOptions, KnexAdapterParams } from '@feathersjs/knex' import { Knex } from 'knex' import { UserInterface } from '@etherealengine/common/src/interfaces/User' -import { staticResourcePath, StaticResourceType } from '@etherealengine/engine/src/schemas/media/static-resource.schema' +import { staticResourcePath } from '@etherealengine/engine/src/schemas/media/static-resource.schema' import { AvatarData, AvatarDatabaseType, @@ -85,7 +85,7 @@ export class AvatarService - await Promise.all( - avatars.data.map(async (avatar) => { - if (avatar.modelResourceId) - try { - //TODO: Remove `as StaticResourceType` once static-resource service is migrated to feathers 5. - avatar.modelResource = (await this.app - .service(staticResourcePath) - .get(avatar.modelResourceId)) as StaticResourceType - } catch (err) { - logger.error(err) - } - if (avatar.thumbnailResourceId) - try { - //TODO: Remove `as StaticResourceType` once static-resource service is migrated to feathers 5. - avatar.thumbnailResource = (await this.app - .service(staticResourcePath) - .get(avatar.thumbnailResourceId)) as StaticResourceType - } catch (err) { - logger.error(err) - } - - return avatar - }) - ) - - return avatars + return await super._find(params) } async create(data: AvatarData, params?: AvatarParams) { From 8cb73a00a61c52ac44f157c6e253538370f3a287 Mon Sep 17 00:00:00 2001 From: hanzlamateen Date: Wed, 12 Jul 2023 12:15:17 +0500 Subject: [PATCH 5/7] Added migration file --- .../migrations/20230711175829_avatar.ts | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 packages/server-core/src/user/avatar/migrations/20230711175829_avatar.ts diff --git a/packages/server-core/src/user/avatar/migrations/20230711175829_avatar.ts b/packages/server-core/src/user/avatar/migrations/20230711175829_avatar.ts new file mode 100644 index 00000000000..15543fcb063 --- /dev/null +++ b/packages/server-core/src/user/avatar/migrations/20230711175829_avatar.ts @@ -0,0 +1,42 @@ +import type { Knex } from 'knex' + +import { avatarPath } from '@etherealengine/engine/src/schemas/user/avatar.schema' + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +export async function up(knex: Knex): Promise { + const tableExists = await knex.schema.hasTable(avatarPath) + + if (tableExists === false) { + await knex.schema.createTable(avatarPath, (table) => { + //@ts-ignore + table.uuid('id').collate('utf8mb4_bin').primary() + table.string('name', 255).nullable() + table.string('identifierName', 255).nullable().unique() + table.string('modelResourceId', 255).nullable() + table.string('thumbnailResourceId', 255).nullable() + table.boolean('isPublic').defaultTo(true) + //@ts-ignore + table.uuid('userId').collate('utf8mb4_bin').nullable() + table.string('project', 255).nullable() + table.dateTime('createdAt').notNullable() + table.dateTime('updatedAt').notNullable() + + table.foreign('userId').references('id').inTable('user').onDelete('SET NULL').onUpdate('CASCADE') + }) + } +} + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +export async function down(knex: Knex): Promise { + const tableExists = await knex.schema.hasTable(avatarPath) + + if (tableExists === true) { + await knex.schema.dropTable(avatarPath) + } +} From 440de672256848d7edb93a0bc2c452131fc71baa Mon Sep 17 00:00:00 2001 From: hanzlamateen Date: Wed, 12 Jul 2023 12:15:40 +0500 Subject: [PATCH 6/7] Added migration file --- .../migrations/20230711175829_avatar.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/server-core/src/user/avatar/migrations/20230711175829_avatar.ts b/packages/server-core/src/user/avatar/migrations/20230711175829_avatar.ts index 15543fcb063..7d28045eec7 100644 --- a/packages/server-core/src/user/avatar/migrations/20230711175829_avatar.ts +++ b/packages/server-core/src/user/avatar/migrations/20230711175829_avatar.ts @@ -1,3 +1,28 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + import type { Knex } from 'knex' import { avatarPath } from '@etherealengine/engine/src/schemas/user/avatar.schema' From 6531564255cda12fa04f190a149e82d3c0b3f033 Mon Sep 17 00:00:00 2001 From: hanzlamateen Date: Wed, 12 Jul 2023 13:01:27 +0500 Subject: [PATCH 7/7] Fixed ts errors --- packages/client-core/src/admin/services/AvatarService.ts | 4 ++-- packages/client-core/src/user/services/AvatarService.ts | 6 ++++-- .../server-core/src/projects/project/project.class.ts | 8 ++++---- packages/server-core/src/user/avatar/avatar-helper.ts | 5 +++-- .../src/user/identity-provider/identity-provider.class.ts | 6 ++++-- packages/server-core/src/user/strategies/discord.ts | 6 +++--- packages/server-core/src/user/strategies/facebook.ts | 6 +++--- packages/server-core/src/user/strategies/github.ts | 5 +++-- packages/server-core/src/user/strategies/google.ts | 4 ++-- packages/server-core/src/user/strategies/linkedin.ts | 6 +++--- packages/server-core/src/user/strategies/twitter.ts | 6 +++--- 11 files changed, 34 insertions(+), 28 deletions(-) diff --git a/packages/client-core/src/admin/services/AvatarService.ts b/packages/client-core/src/admin/services/AvatarService.ts index 84676b7c85b..209ca7f744f 100644 --- a/packages/client-core/src/admin/services/AvatarService.ts +++ b/packages/client-core/src/admin/services/AvatarService.ts @@ -98,7 +98,7 @@ export const AdminAvatarService = { } const adminAvatarState = getMutableState(AdminAvatarState) const limit = adminAvatarState.limit.value - const avatars = await API.instance.client.service(avatarPath).find({ + const avatars = (await API.instance.client.service(avatarPath).find({ query: { admin: true, $sort: { @@ -108,7 +108,7 @@ export const AdminAvatarService = { $skip: skip * AVATAR_PAGE_LIMIT, search: search } - }) + })) as Paginated dispatchAction(AdminAvatarActions.avatarsFetched({ avatars })) }, removeAdminAvatar: async (id: string) => { diff --git a/packages/client-core/src/user/services/AvatarService.ts b/packages/client-core/src/user/services/AvatarService.ts index 022b705de9e..6c0de24d70d 100644 --- a/packages/client-core/src/user/services/AvatarService.ts +++ b/packages/client-core/src/user/services/AvatarService.ts @@ -23,6 +23,8 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ +import { Paginated } from '@feathersjs/feathers' + import { EntityUUID } from '@etherealengine/common/src/interfaces/EntityUUID' import { StaticResourceInterface } from '@etherealengine/common/src/interfaces/StaticResourceInterface' import { UserId } from '@etherealengine/common/src/interfaces/UserId' @@ -84,13 +86,13 @@ export const AvatarService = { const skip = getState(AvatarState).skip const newSkip = incDec === 'increment' ? skip + AVATAR_PAGE_LIMIT : incDec === 'decrement' ? skip - AVATAR_PAGE_LIMIT : skip - const result = await Engine.instance.api.service(avatarPath).find({ + const result = (await Engine.instance.api.service(avatarPath).find({ query: { search, $skip: newSkip, $limit: AVATAR_PAGE_LIMIT } - }) + })) as Paginated dispatchAction( AvatarActions.updateAvatarListAction({ avatarList: result.data, search, skip: result.skip, total: result.total }) ) diff --git a/packages/server-core/src/projects/project/project.class.ts b/packages/server-core/src/projects/project/project.class.ts index 0cc4d431978..9916041f437 100644 --- a/packages/server-core/src/projects/project/project.class.ts +++ b/packages/server-core/src/projects/project/project.class.ts @@ -24,7 +24,7 @@ Ethereal Engine. All Rights Reserved. */ import { BadRequest, Forbidden } from '@feathersjs/errors' -import { Id, Params } from '@feathersjs/feathers' +import { Id, Paginated, Params } from '@feathersjs/feathers' import appRootPath from 'app-root-path' import { SequelizeServiceOptions, Service } from 'feathers-sequelize' import fs from 'fs' @@ -40,7 +40,7 @@ import { import { UserInterface } from '@etherealengine/common/src/interfaces/User' import { processFileName } from '@etherealengine/common/src/utils/processFileName' import { routePath, RouteType } from '@etherealengine/engine/src/schemas/route/route.schema' -import { avatarPath } from '@etherealengine/engine/src/schemas/user/avatar.schema' +import { avatarPath, AvatarType } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { getState } from '@etherealengine/hyperflux' import templateProjectJson from '@etherealengine/projects/template-project/package.json' @@ -609,7 +609,7 @@ export class Project extends Service { await this.app.service(routePath).remove(route.id) }) - const avatarItems = await this.app.service(avatarPath).find({ + const avatarItems = (await this.app.service(avatarPath).find({ query: { $and: [ { @@ -622,7 +622,7 @@ export class Project extends Service { } ] } - }) + })) as Paginated await Promise.all( avatarItems.data.map(async (avatar) => { diff --git a/packages/server-core/src/user/avatar/avatar-helper.ts b/packages/server-core/src/user/avatar/avatar-helper.ts index 041d62b3b6a..5643d2b6a70 100644 --- a/packages/server-core/src/user/avatar/avatar-helper.ts +++ b/packages/server-core/src/user/avatar/avatar-helper.ts @@ -23,6 +23,7 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ +import { Paginated } from '@feathersjs/feathers' import appRootPath from 'app-root-path' import fs from 'fs' import path from 'path' @@ -116,7 +117,7 @@ export const installAvatarsFromProject = async (app: Application, avatarsFolder: await Promise.all( avatarsToInstall.map(async (avatar) => { try { - const existingAvatar = await app.service(avatarPath).find({ + const existingAvatar = (await app.service(avatarPath).find({ query: { name: avatar.avatarName, isPublic: true, @@ -129,7 +130,7 @@ export const installAvatarsFromProject = async (app: Application, avatarsFolder: } ] } - }) + })) as Paginated console.log({ existingAvatar }) let selectedAvatar: AvatarType diff --git a/packages/server-core/src/user/identity-provider/identity-provider.class.ts b/packages/server-core/src/user/identity-provider/identity-provider.class.ts index 4abffa9b9b2..e4e6a53fe8a 100755 --- a/packages/server-core/src/user/identity-provider/identity-provider.class.ts +++ b/packages/server-core/src/user/identity-provider/identity-provider.class.ts @@ -32,7 +32,7 @@ import { v1 as uuidv1 } from 'uuid' import { isDev } from '@etherealengine/common/src/config' import { IdentityProviderInterface } from '@etherealengine/common/src/dbmodels/IdentityProvider' import { UserInterface } from '@etherealengine/common/src/interfaces/User' -import { avatarPath } from '@etherealengine/engine/src/schemas/user/avatar.schema' +import { avatarPath, AvatarType } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { Application } from '../../../declarations' import appConfig from '../../appconfig' @@ -198,7 +198,9 @@ export class IdentityProvider extends Service } ] }) - const avatars = await this.app.service(avatarPath).find({ isInternal: true, query: { $limit: 1000 } }) + const avatars = (await this.app + .service(avatarPath) + .find({ isInternal: true, query: { $limit: 1000 } })) as Paginated let isGuest = type === 'guest' diff --git a/packages/server-core/src/user/strategies/discord.ts b/packages/server-core/src/user/strategies/discord.ts index 2db691d4553..5f66b0f2595 100755 --- a/packages/server-core/src/user/strategies/discord.ts +++ b/packages/server-core/src/user/strategies/discord.ts @@ -24,11 +24,11 @@ Ethereal Engine. All Rights Reserved. */ import { AuthenticationRequest } from '@feathersjs/authentication' -import { Params } from '@feathersjs/feathers' +import { Paginated, Params } from '@feathersjs/feathers' import { random } from 'lodash' import { UserInterface } from '@etherealengine/common/src/interfaces/User' -import { avatarPath } from '@etherealengine/engine/src/schemas/user/avatar.schema' +import { avatarPath, AvatarType } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { Application } from '../../../declarations' import config from '../../appconfig' @@ -66,7 +66,7 @@ export class DiscordStrategy extends CustomOAuthStrategy { {} ) if (!entity.userId) { - const avatars = await this.app.service(avatarPath).find({ isInternal: true }) + const avatars = (await this.app.service(avatarPath).find({ isInternal: true })) as Paginated const code = await getFreeInviteCode(this.app) const newUser = (await this.app.service('user').create({ isGuest: false, diff --git a/packages/server-core/src/user/strategies/facebook.ts b/packages/server-core/src/user/strategies/facebook.ts index 07ddc1b3240..ff6ea7f23d7 100755 --- a/packages/server-core/src/user/strategies/facebook.ts +++ b/packages/server-core/src/user/strategies/facebook.ts @@ -24,11 +24,11 @@ Ethereal Engine. All Rights Reserved. */ import { AuthenticationRequest } from '@feathersjs/authentication' -import { Params } from '@feathersjs/feathers' +import { Paginated, Params } from '@feathersjs/feathers' import { random } from 'lodash' import { UserInterface } from '@etherealengine/common/src/interfaces/User' -import { avatarPath } from '@etherealengine/engine/src/schemas/user/avatar.schema' +import { avatarPath, AvatarType } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { Application } from '../../../declarations' import config from '../../appconfig' @@ -66,7 +66,7 @@ export class FacebookStrategy extends CustomOAuthStrategy { {} ) if (!entity.userId) { - const avatars = await this.app.service(avatarPath).find({ isInternal: true }) + const avatars = (await this.app.service(avatarPath).find({ isInternal: true })) as Paginated const code = await getFreeInviteCode(this.app) const newUser = (await this.app.service('user').create({ isGuest: false, diff --git a/packages/server-core/src/user/strategies/github.ts b/packages/server-core/src/user/strategies/github.ts index c77af17801f..9a68530387b 100755 --- a/packages/server-core/src/user/strategies/github.ts +++ b/packages/server-core/src/user/strategies/github.ts @@ -24,10 +24,11 @@ Ethereal Engine. All Rights Reserved. */ import { AuthenticationRequest } from '@feathersjs/authentication' +import { Paginated } from '@feathersjs/feathers' import { random } from 'lodash' import { UserInterface } from '@etherealengine/common/src/interfaces/User' -import { avatarPath } from '@etherealengine/engine/src/schemas/user/avatar.schema' +import { avatarPath, AvatarType } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { Application } from '../../../declarations' import config from '../../appconfig' @@ -67,7 +68,7 @@ export class GithubStrategy extends CustomOAuthStrategy { {} ) if (!entity.userId) { - const avatars = await this.app.service(avatarPath).find({ isInternal: true }) + const avatars = (await this.app.service(avatarPath).find({ isInternal: true })) as Paginated const code = await getFreeInviteCode(this.app) const newUser = (await this.app.service('user').create({ isGuest: false, diff --git a/packages/server-core/src/user/strategies/google.ts b/packages/server-core/src/user/strategies/google.ts index 946b078e7c6..c9c00ef8379 100755 --- a/packages/server-core/src/user/strategies/google.ts +++ b/packages/server-core/src/user/strategies/google.ts @@ -28,7 +28,7 @@ import { Paginated, Params } from '@feathersjs/feathers' import { random } from 'lodash' import { UserInterface } from '@etherealengine/common/src/interfaces/User' -import { avatarPath } from '@etherealengine/engine/src/schemas/user/avatar.schema' +import { avatarPath, AvatarType } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { Application } from '../../../declarations' import config from '../../appconfig' @@ -66,7 +66,7 @@ export class Googlestrategy extends CustomOAuthStrategy { {} ) if (!entity.userId) { - const avatars = await this.app.service(avatarPath).find({ isInternal: true }) + const avatars = (await this.app.service(avatarPath).find({ isInternal: true })) as Paginated const code = await getFreeInviteCode(this.app) const newUser = (await this.app.service('user').create({ isGuest: false, diff --git a/packages/server-core/src/user/strategies/linkedin.ts b/packages/server-core/src/user/strategies/linkedin.ts index 1eb7db0919e..3c9c3042287 100755 --- a/packages/server-core/src/user/strategies/linkedin.ts +++ b/packages/server-core/src/user/strategies/linkedin.ts @@ -24,11 +24,11 @@ Ethereal Engine. All Rights Reserved. */ import { AuthenticationRequest } from '@feathersjs/authentication' -import { Params } from '@feathersjs/feathers' +import { Paginated, Params } from '@feathersjs/feathers' import { random } from 'lodash' import { UserInterface } from '@etherealengine/common/src/interfaces/User' -import { avatarPath } from '@etherealengine/engine/src/schemas/user/avatar.schema' +import { avatarPath, AvatarType } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { Application } from '../../../declarations' import config from '../../appconfig' @@ -66,7 +66,7 @@ export class LinkedInStrategy extends CustomOAuthStrategy { {} ) if (!entity.userId) { - const avatars = await this.app.service(avatarPath).find({ isInternal: true }) + const avatars = (await this.app.service(avatarPath).find({ isInternal: true })) as Paginated const code = await getFreeInviteCode(this.app) const newUser = (await this.app.service('user').create({ isGuest: false, diff --git a/packages/server-core/src/user/strategies/twitter.ts b/packages/server-core/src/user/strategies/twitter.ts index 7b15956daf5..98cd16f0449 100755 --- a/packages/server-core/src/user/strategies/twitter.ts +++ b/packages/server-core/src/user/strategies/twitter.ts @@ -24,11 +24,11 @@ Ethereal Engine. All Rights Reserved. */ import { AuthenticationRequest } from '@feathersjs/authentication' -import { Params } from '@feathersjs/feathers' +import { Paginated, Params } from '@feathersjs/feathers' import { random } from 'lodash' import { UserInterface } from '@etherealengine/common/src/interfaces/User' -import { avatarPath } from '@etherealengine/engine/src/schemas/user/avatar.schema' +import { avatarPath, AvatarType } from '@etherealengine/engine/src/schemas/user/avatar.schema' import { Application } from '../../../declarations' import config from '../../appconfig' @@ -67,7 +67,7 @@ export class TwitterStrategy extends CustomOAuthStrategy { {} ) if (!entity.userId) { - const avatars = await this.app.service(avatarPath).find({ isInternal: true }) + const avatars = (await this.app.service(avatarPath).find({ isInternal: true })) as Paginated const code = await getFreeInviteCode(this.app) const newUser = (await this.app.service('user').create({ isGuest: false,