diff --git a/app/src/components/UserDeletionVRSpacesQuestionModal.vue b/app/src/components/UserDeletionVRSpacesQuestionModal.vue new file mode 100644 index 00000000..836f69fc --- /dev/null +++ b/app/src/components/UserDeletionVRSpacesQuestionModal.vue @@ -0,0 +1,184 @@ + + + \ No newline at end of file diff --git a/app/src/stores/vrSpaceStore.ts b/app/src/stores/vrSpaceStore.ts index 50569ebb..7306798c 100644 --- a/app/src/stores/vrSpaceStore.ts +++ b/app/src/stores/vrSpaceStore.ts @@ -129,6 +129,14 @@ export const useVrSpaceStore = defineStore('vrSpace', () => { return vrSpaceId; } + async function transferVrSpaceOwnership(fromUserId: string, toUserId: string) { + await connection.client.vr.transferVrSpaceOwnership.mutate({ fromUserId, toUserId }); + } + + async function doesUserHaveVrSpaces(userId: string) { + return await connection.client.vr.doesUserHaveVrSpaces.mutate({ userId }); + } + async function deleteVrSpace(vrSpaceId?: VrSpaceId) { if (!vrSpaceId) vrSpaceId = currentVrSpace.value?.dbData.vrSpaceId; if (!vrSpaceId) { @@ -237,6 +245,8 @@ export const useVrSpaceStore = defineStore('vrSpace', () => { navMeshUrl, worldModelScaleString, panoramicPreviewUrl, + doesUserHaveVrSpaces, + transferVrSpaceOwnership, createVrSpace, deleteVrSpace, enterVrSpace, diff --git a/app/src/views/admin/AdminUserManagerView.vue b/app/src/views/admin/AdminUserManagerView.vue index 10fd4dbc..5663142b 100644 --- a/app/src/views/admin/AdminUserManagerView.vue +++ b/app/src/views/admin/AdminUserManagerView.vue @@ -92,7 +92,10 @@ class="btn"> edit - @@ -104,18 +107,29 @@ Du saknar behörighet till den här sidan. + diff --git a/backend/mediaserver/src/routers/vrRouter.ts b/backend/mediaserver/src/routers/vrRouter.ts index 697839a8..93ce47f5 100644 --- a/backend/mediaserver/src/routers/vrRouter.ts +++ b/backend/mediaserver/src/routers/vrRouter.ts @@ -4,7 +4,7 @@ process.env.DEBUG = 'Router:VR*, ' + process.env.DEBUG; log.enable(process.env.DEBUG); import { ClientRealtimeDataSchema, hasAtLeastSecurityRole, PlacedObjectIdSchema, PlacedObjectInsertSchema, ScreenShareSchema, TransformSchema, VrSpaceIdSchema, VrSpaceUpdateSchema } from 'schemas'; -import { procedure as p, router, isUserClientM, userClientP, atLeastUserP, userInVrSpaceP, userWithEditRightsToVrSpace, userWithAdminRightsToVrSpace } from '../trpc/trpc.js'; +import { procedure as p, router, isUserClientM, userClientP, atLeastUserP, atLeastAdminP, userInVrSpaceP, userWithEditRightsToVrSpace, userWithAdminRightsToVrSpace } from '../trpc/trpc.js'; import { VrSpace } from 'classes/VrSpace.js'; import { z } from 'zod'; import { db, schema, } from 'database'; @@ -89,6 +89,57 @@ export const vrRouter = router({ ctx.client.leaveCurrentVrSpace(); // ctx.vrSpace.removeClient(ctx.client); }), + transferVrSpaceOwnership: atLeastUserP.input(z.object({ + fromUserId: z.string(), + toUserId: z.string(), + })).mutation(async ({ ctx, input }) => { + if (!hasAtLeastSecurityRole(ctx.role, 'admin')) { + throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Admin rights required' }); + } + const { fromUserId, toUserId } = input; + + log.info(`Transferring VR spaces from ${fromUserId} to ${toUserId}`); + + const result = await db + .update(schema.vrSpaces) + .set({ ownerUserId: toUserId }) + .where(eq(schema.vrSpaces.ownerUserId, fromUserId)) + .returning({ + vrSpaceId: schema.vrSpaces.vrSpaceId, + name: schema.vrSpaces.name + }); + + log.info(`Transferred ${result.length} VR spaces`); + + for (const space of result) { + const vrSpace = VrSpace.getVrSpace(space.vrSpaceId); + if (vrSpace) { + vrSpace.reloadDbData('ownership transferred'); + } + } + + return { + transferredCount: result.length, + spaces: result + }; + }), + doesUserHaveVrSpaces: atLeastAdminP.input(z.object({ userId: z.string() })) + .mutation(async ({ ctx, input }) => { + + const { userId } = input; + log.info(`Checking if user ${userId} owns VR spaces`); + + + const result = await db + .select({ count: sql`count(*)`.mapWith(Number) }) + .from(schema.vrSpaces) + .where(eq(schema.vrSpaces.ownerUserId, userId)); + + const hasSpaces = result[0]?.count > 0; + log.info(`User ${userId} ${hasSpaces ? 'HAS' : 'has NO'} VR spaces`); + + return hasSpaces; + }), updateVrSpace: userWithEditRightsToVrSpace.input(VrSpaceUpdateSchema).mutation(async ({ ctx, input }) => { log.info('updating vrSpace', input); const { vrSpaceId, reason, ...data } = input;