Skip to content
This repository has been archived by the owner on Aug 21, 2024. It is now read-only.

Commit

Permalink
IR-3329: Added Visibility column in project table (#10722)
Browse files Browse the repository at this point in the history
* feat: Add project visibility toggle functionality

* feat: Add project visibility column to admin table

* feat: Remove unnecessary verify-project-owner hook and replace with verify-project-permission

* refactor: Update project-permission.hooks.ts to set project id in query

* Fixed order

* refactor: Set project ID in query for project-permission.hooks.ts

* Fixed string enum

* chore: Update ProjectTable component and ProjectService

* Removed ProjectVisibility enum

---------

Co-authored-by: hanzlamateen <[email protected]>
Co-authored-by: Josh Field <[email protected]>
  • Loading branch information
3 people authored Jul 30, 2024
1 parent 9b7a105 commit ed90277
Show file tree
Hide file tree
Showing 11 changed files with 141 additions and 92 deletions.
1 change: 1 addition & 0 deletions packages/client-core/i18n/en/admin.json
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@
"name": "Name",
"projectVersion": "Version",
"enabled": "Enabled",
"visibility": "Visibility",
"commitSHA": "Commit SHA",
"commitDate": "Commit Date",
"actions": "Actions"
Expand Down
3 changes: 2 additions & 1 deletion packages/client-core/src/admin/common/constants/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { t } from 'i18next'

import { ITableHeadCell } from '../Table'

type IdType = 'name' | 'projectVersion' | 'enabled' | 'commitSHA' | 'commitDate' | 'actions'
type IdType = 'name' | 'projectVersion' | 'enabled' | 'visibility' | 'commitSHA' | 'commitDate' | 'actions'

export type ProjectRowType = Record<IdType, string | JSX.Element | undefined>

Expand All @@ -39,6 +39,7 @@ export const projectsColumns: IProjectColumn[] = [
{ id: 'name', sortable: true, label: t('admin:components.project.columns.name') },
{ id: 'projectVersion', label: t('admin:components.project.columns.projectVersion') },
{ id: 'enabled', label: t('admin:components.project.columns.enabled') },
{ id: 'visibility', label: t('admin:components.project.columns.visibility') },
{ id: 'commitSHA', label: t('admin:components.project.columns.commitSHA') },
{ id: 'commitDate', sortable: true, label: t('admin:components.project.columns.commitDate') },
{ id: 'actions', label: t('admin:components.project.columns.actions') }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import { PopoverState } from '@etherealengine/client-core/src/common/services/Po
import { ProjectService } from '@etherealengine/client-core/src/common/services/ProjectService'
import config from '@etherealengine/common/src/config'
import multiLogger from '@etherealengine/common/src/logger'
import { projectPath, ProjectType } from '@etherealengine/common/src/schema.type.module'
import { ProjectType, projectPath } from '@etherealengine/common/src/schema.type.module'
import { getMutableState, useHookstate } from '@etherealengine/hyperflux'
import { useFind, useSearch } from '@etherealengine/spatial/src/common/functions/FeathersHooks'
import ConfirmDialog from '@etherealengine/ui/src/components/tailwind/ConfirmDialog'
Expand All @@ -50,8 +50,8 @@ import Toggle from '@etherealengine/ui/src/primitives/tailwind/Toggle'
import Tooltip from '@etherealengine/ui/src/primitives/tailwind/Tooltip'

import { toDisplayDateTime } from '@etherealengine/common/src/utils/datetime-sql'
import { ProjectRowType, projectsColumns } from '../../common/constants/project'
import DataTable from '../../common/Table'
import { ProjectRowType, projectsColumns } from '../../common/constants/project'
import { ProjectUpdateState } from '../../services/ProjectUpdateService'
import AddEditProjectModal from './AddEditProjectModal'
import ManageUserPermissionModal from './ManageUserPermissionModal'
Expand Down Expand Up @@ -91,6 +91,11 @@ export default function ProjectTable(props: { search: string }) {
projectQuery.refetch()
}

const handleVisibilityChange = async (project: ProjectType) => {
await ProjectService.setVisibility(project.id, project.visibility === 'private' ? 'public' : 'private')
projectQuery.refetch()
}

const RowActions = ({ project }: { project: ProjectType }) => {
const handleProjectUpdate = async () => {
const projectUpdateStatus = getMutableState(ProjectUpdateState)[project.name].value
Expand Down Expand Up @@ -235,6 +240,7 @@ export default function ProjectTable(props: { search: string }) {
onChange={() => handleEnabledChange(row)}
/>
),
visibility: <Toggle value={row.visibility === 'public'} onChange={() => handleVisibilityChange(row)} />,
commitSHA: (
<span className="flex items-center justify-between">
<Tooltip title={row.commitSHA || ''}>
Expand Down
11 changes: 11 additions & 0 deletions packages/client-core/src/common/services/ProjectService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,17 @@ export const ProjectService = {
}
},

setVisibility: async (id: string, visibility: ProjectType['visibility']) => {
try {
await Engine.instance.api.service(projectPath).patch(id, {
visibility
})
} catch (err) {
logger.error(err, 'Error setting project visibility')
throw err
}
},

setRepositoryPath: async (id: string, url: string) => {
try {
await Engine.instance.api.service(projectPath).patch(id, {
Expand Down
2 changes: 2 additions & 0 deletions packages/common/src/schemas/projects/project.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export const projectSchema = Type.Object(
commitSHA: Type.Optional(Type.String()),
commitDate: Type.Optional(Type.String({ format: 'date-time' })),
assetsOnly: Type.Boolean(),
visibility: StringEnum(['private', 'public']),
settings: Type.Optional(Type.Array(Type.Ref(projectSettingSchema))),
createdAt: Type.String({ format: 'date-time' }),
updatedAt: Type.String({ format: 'date-time' })
Expand Down Expand Up @@ -103,6 +104,7 @@ export const projectQueryProperties = Type.Pick(projectSchema, [
'updateSchedule',
'updateUserId',
'hasWriteAccess',
'visibility',
'commitSHA',
'commitDate'
])
Expand Down
22 changes: 15 additions & 7 deletions packages/server-core/src/hooks/resolve-projects-by-permission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20
Ethereal Engine. All Rights Reserved.
*/

import { UserType, projectPath, projectPermissionPath } from '@etherealengine/common/src/schema.type.module'
import {
ProjectType,
UserType,
projectPath,
projectPermissionPath
} from '@etherealengine/common/src/schema.type.module'
import { Forbidden } from '@feathersjs/errors'
import { Application, HookContext } from '../../declarations'
/**
Expand All @@ -43,17 +48,20 @@ export default () => {
paginate: false
})

if (data.length === 0) {
const allowedProjects = (await context.app.service(projectPath).find({
query: {
$or: [{ visibility: 'public' }, { id: { $in: data.map((projectPermission) => projectPermission.projectId) } }]
},
paginate: false
})) as any as ProjectType[]

if (allowedProjects.length === 0) {
console.error(`No Project permissions found. UserId: ${loggedInUser.id}`)
throw new Forbidden(`Project permissions not found`)
}

context.params.query.project = { $in: [] }
context.params.query.project = { $in: allowedProjects.map((project) => project.name) }

for (const projP of data) {
const project = await context.app.service(projectPath).get(projP.projectId)
if (project !== undefined) context.params.query.project.$in.push(project.name)
}
return context
}
return context
Expand Down
74 changes: 0 additions & 74 deletions packages/server-core/src/hooks/verify-project-owner.ts

This file was deleted.

10 changes: 7 additions & 3 deletions packages/server-core/src/hooks/verify-project-permission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,20 @@ export default (types: string[]) => {
projectId = context.params.query.projectId
} else if (context.data?.projectId) {
projectId = context.data.projectId
} else if (context.id) {
} else if (context.id && context.path === projectPath) {
projectId = context.id.toString()
} else {
throw new BadRequest('Missing project ID in request')
}

if (!projectId) throw new BadRequest('Missing project ID in request')

const project = await context.app.service(projectPath).get(projectId)

if (!project) throw new NotFound('Project not found')

if (project.visibility === 'public') {
return context
}

const { data } = (await context.app.service(projectPermissionPath).find({
query: {
userId: loggedInUser.id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import logger from '../../ServerLogger'
import checkScopeHook from '../../hooks/check-scope'
import enableClientPagination from '../../hooks/enable-client-pagination'
import resolveProjectId from '../../hooks/resolve-project-id'
import verifyProjectOwner from '../../hooks/verify-project-owner'
import verifyProjectPermission from '../../hooks/verify-project-permission'
import { ProjectPermissionService } from './project-permission.class'
import {
projectPermissionDataResolver,
Expand Down Expand Up @@ -200,6 +200,30 @@ const makeRandomProjectOwner = async (context: HookContext<ProjectPermissionServ
if (context.id && context.result) await context.service.makeRandomProjectOwnerIfNone(result[0].projectId)
}

/**
* Gets project id from permission and sets it in query
* @param context
* @returns
*/
const resolvePermissionId = async (context: HookContext<ProjectPermissionService>) => {
if (context.id && typeof context.id === 'string') {
const project = (await context.app.service(projectPermissionPath).find({
query: {
id: context.id,
$limit: 1
}
})) as Paginated<ProjectPermissionType>

if (project.data.length === 0) throw new BadRequest('Invalid project-permission ID')

const projectId = project.data[0].projectId

context.params.query = { ...context.params.query, projectId }

return context
}
}

export default {
around: {
all: [
Expand All @@ -221,7 +245,10 @@ export default {
],
get: [],
create: [
iff(isProvider('external'), verifyProjectOwner()),
iff(
isProvider('external'),
iffElse(checkScopeHook('projects', 'write'), [], [resolvePermissionId, verifyProjectPermission(['owner'])])
),
schemaHooks.validateData(projectPermissionDataValidator),
schemaHooks.resolveData(projectPermissionDataResolver),
setLoggedInUserData('createdBy'),
Expand All @@ -230,12 +257,20 @@ export default {
],
update: [disallow()],
patch: [
iff(isProvider('external'), verifyProjectOwner()),
iff(
isProvider('external'),
iffElse(checkScopeHook('projects', 'write'), [], [resolvePermissionId, verifyProjectPermission(['owner'])])
),
schemaHooks.validateData(projectPermissionPatchValidator),
schemaHooks.resolveData(projectPermissionPatchResolver),
ensureTypeInPatch
],
remove: [iff(isProvider('external'), verifyProjectOwner())]
remove: [
iff(
isProvider('external'),
iffElse(checkScopeHook('projects', 'write'), [], [resolvePermissionId, verifyProjectPermission(['owner'])])
)
]
},

after: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
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 { projectPath } from '@etherealengine/common/src/schema.type.module'

/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
export async function up(knex: Knex): Promise<void> {
const visibilityColumnExists = await knex.schema.hasColumn(projectPath, 'visibility')
if (!visibilityColumnExists) {
await knex.schema.alterTable(projectPath, async (table) => {
table.string('visibility', 255).defaultTo('private')
})
}
}

/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
export async function down(knex: Knex): Promise<void> {
const visibilityColumnExists = await knex.schema.hasColumn(projectPath, 'visibility')

if (visibilityColumnExists) {
await knex.schema.alterTable(projectPath, async (table) => {
table.dropColumn('visibility')
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export default {
[],
[
iffElse(
checkScope('editor', 'read'),
checkScope('editor', 'write'),
verifyProjectPermission(['owner', 'editor', 'reviewer']),
setInContext('type', 'public')
) as any
Expand Down

0 comments on commit ed90277

Please sign in to comment.