From d68e241f456d33545b1d734e3fd73682a5f2c672 Mon Sep 17 00:00:00 2001 From: Philip Okugbe <16838612+Philipinho@users.noreply.github.com> Date: Sat, 20 Jun 2026 14:27:41 +0100 Subject: [PATCH] feat(ee): personal spaces (#2298) * feat(ee): personal spaces * pref * feat: on-demand only * error notification --- .../public/locales/en-US/translation.json | 8 +- .../components/layouts/global/top-menu.tsx | 39 ++++++++++ apps/client/src/ee/features.ts | 1 + .../create-personal-space-modal.tsx | 78 +++++++++++++++++++ .../components/personal-spaces-setting.tsx | 64 +++++++++++++++ .../queries/personal-space-query.ts | 34 ++++++++ .../services/personal-space-service.ts | 14 ++++ .../space/components/create-space-form.tsx | 4 +- .../space/components/edit-space-form.tsx | 4 +- .../src/features/space/types/space.types.ts | 1 + .../workspace/types/workspace.types.ts | 6 ++ .../settings/workspace/workspace-settings.tsx | 4 + apps/client/src/styles/a11y-overrides.css | 8 ++ apps/server/src/common/features.ts | 1 + .../src/core/space/dto/create-space.dto.ts | 7 +- .../src/core/space/services/space.service.ts | 5 ++ .../workspace/dto/update-workspace.dto.ts | 4 + .../workspace/services/workspace.service.ts | 30 ++++++- .../20260620T010047-personal-spaces.ts | 24 ++++++ .../src/database/repos/space/space.repo.ts | 16 ++++ .../repos/workspace/workspace.repo.ts | 20 +++++ apps/server/src/database/types/db.d.ts | 1 + apps/server/src/ee | 2 +- 23 files changed, 366 insertions(+), 9 deletions(-) create mode 100644 apps/client/src/ee/personal-space/components/create-personal-space-modal.tsx create mode 100644 apps/client/src/ee/personal-space/components/personal-spaces-setting.tsx create mode 100644 apps/client/src/ee/personal-space/queries/personal-space-query.ts create mode 100644 apps/client/src/ee/personal-space/services/personal-space-service.ts create mode 100644 apps/server/src/database/migrations/20260620T010047-personal-spaces.ts diff --git a/apps/client/public/locales/en-US/translation.json b/apps/client/public/locales/en-US/translation.json index aa20b9144..10e6588a4 100644 --- a/apps/client/public/locales/en-US/translation.json +++ b/apps/client/public/locales/en-US/translation.json @@ -1087,5 +1087,11 @@ "Added {{name}} to favorites": "Added {{name}} to favorites", "Removed {{name}} from favorites": "Removed {{name}} from favorites", "Page menu for {{name}}": "Page menu for {{name}}", - "Create subpage of {{name}}": "Create subpage of {{name}}" + "Create subpage of {{name}}": "Create subpage of {{name}}", + "Allow personal spaces": "Allow personal spaces", + "Members can create their own personal space.": "Members can create their own personal space.", + "Toggle allow personal spaces": "Toggle allow personal spaces", + "Create personal space": "Create personal space", + "Personal space": "Personal space", + "{{name}}'s space": "{{name}}'s space" } diff --git a/apps/client/src/components/layouts/global/top-menu.tsx b/apps/client/src/components/layouts/global/top-menu.tsx index 849250800..9ee13e9c9 100644 --- a/apps/client/src/components/layouts/global/top-menu.tsx +++ b/apps/client/src/components/layouts/global/top-menu.tsx @@ -15,9 +15,16 @@ import { IconMoon, IconSettings, IconSun, + IconUser, IconUserCircle, IconUsers, } from "@tabler/icons-react"; +import { useDisclosure } from "@mantine/hooks"; +import { getSpaceUrl } from "@/lib/config.ts"; +import { useHasFeature } from "@/ee/hooks/use-feature"; +import { Feature } from "@/ee/features"; +import { usePersonalSpaceQuery } from "@/ee/personal-space/queries/personal-space-query"; +import CreatePersonalSpaceModal from "@/ee/personal-space/components/create-personal-space-modal"; import { useAtom } from "jotai"; import { currentUserAtom } from "@/features/user/atoms/current-user-atom.ts"; import { Link } from "react-router-dom"; @@ -36,11 +43,20 @@ export default function TopMenu() { const user = currentUser?.user; const workspace = currentUser?.workspace; + const hasPersonalSpaces = useHasFeature(Feature.PERSONAL_SPACES); + const settingEnabled = workspace?.settings?.spaces?.allowPersonal === true; + const { data: personalSpace } = usePersonalSpaceQuery(hasPersonalSpaces); + const [ + createOpened, + { open: openCreate, close: closeCreate }, + ] = useDisclosure(false); + if (!user || !workspace) { return <>; } return ( + <> @@ -115,6 +131,26 @@ export default function TopMenu() { {t("My preferences")} + {personalSpace ? ( + } + > + {t("Personal space")} + + ) : ( + hasPersonalSpaces && + settingEnabled && ( + } + > + {t("Create personal space")} + + ) + )} + }> @@ -160,5 +196,8 @@ export default function TopMenu() { + + + ); } diff --git a/apps/client/src/ee/features.ts b/apps/client/src/ee/features.ts index 5a23900e3..a087b1328 100644 --- a/apps/client/src/ee/features.ts +++ b/apps/client/src/ee/features.ts @@ -19,5 +19,6 @@ export const Feature = { SHARING_CONTROLS: 'sharing:controls', TEMPLATES: 'templates', VIEWER_COMMENTS: 'comment:viewer', + PERSONAL_SPACES: 'spaces:personal', DOCX_EXPORT: 'export:docx', } as const; diff --git a/apps/client/src/ee/personal-space/components/create-personal-space-modal.tsx b/apps/client/src/ee/personal-space/components/create-personal-space-modal.tsx new file mode 100644 index 000000000..72f1bc2b1 --- /dev/null +++ b/apps/client/src/ee/personal-space/components/create-personal-space-modal.tsx @@ -0,0 +1,78 @@ +import { Modal, TextInput, Button, Group, Divider } from "@mantine/core"; +import { useForm } from "@mantine/form"; +import { zod4Resolver } from "mantine-form-zod-resolver"; +import { z } from "zod/v4"; +import { useNavigate } from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import { useAtomValue } from "jotai"; +import { currentUserAtom } from "@/features/user/atoms/current-user-atom.ts"; +import { useCreatePersonalSpaceMutation } from "@/ee/personal-space/queries/personal-space-query"; +import { getSpaceUrl } from "@/lib/config.ts"; +import { notifications } from "@mantine/notifications"; + +const formSchema = z.object({ + name: z.string().trim().min(2).max(100), +}); +type FormValues = z.infer; + +type Props = { + opened: boolean; + onClose: () => void; +}; + +export default function CreatePersonalSpaceModal({ opened, onClose }: Props) { + const { t } = useTranslation(); + const navigate = useNavigate(); + const currentUser = useAtomValue(currentUserAtom); + const createMutation = useCreatePersonalSpaceMutation(); + + const firstName = (currentUser?.user?.name ?? "").trim().split(/\s+/)[0] || ""; + + const form = useForm({ + validate: zod4Resolver(formSchema), + initialValues: { + name: firstName ? t("{{name}}'s space", { name: firstName }) : "", + }, + }); + + const handleSubmit = async (values: FormValues) => { + try { + const createdSpace = await createMutation.mutateAsync({ + name: values.name, + }); + onClose(); + navigate(getSpaceUrl(createdSpace.slug)); + } catch (err) { + notifications.show({ + message: err?.response?.data?.message, + color: "red", + }); + } + }; + + return ( + + +
+ + + + + +
+ ); +} diff --git a/apps/client/src/ee/personal-space/components/personal-spaces-setting.tsx b/apps/client/src/ee/personal-space/components/personal-spaces-setting.tsx new file mode 100644 index 000000000..77ea884cd --- /dev/null +++ b/apps/client/src/ee/personal-space/components/personal-spaces-setting.tsx @@ -0,0 +1,64 @@ +import { Group, Text, Switch, Tooltip } from "@mantine/core"; +import { useAtom } from "jotai"; +import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { updateWorkspace } from "@/features/workspace/services/workspace-service.ts"; +import { notifications } from "@mantine/notifications"; +import { useHasFeature } from "@/ee/hooks/use-feature"; +import { Feature } from "@/ee/features"; +import { useUpgradeLabel } from "@/ee/hooks/use-upgrade-label.ts"; + +export default function PersonalSpacesSetting() { + const { t } = useTranslation(); + + return ( + +
+ {t("Allow personal spaces")} + + {t("Members can create their own personal space.")} + +
+ + +
+ ); +} + +function PersonalSpacesToggle() { + const { t } = useTranslation(); + const [workspace, setWorkspace] = useAtom(workspaceAtom); + const [checked, setChecked] = useState( + workspace?.settings?.spaces?.allowPersonal === true, + ); + const hasPersonalSpaces = useHasFeature(Feature.PERSONAL_SPACES); + const upgradeLabel = useUpgradeLabel(); + + const handleChange = async (event: React.ChangeEvent) => { + const value = event.currentTarget.checked; + try { + const updatedWorkspace = await updateWorkspace({ + allowPersonalSpaces: value, + }); + setChecked(value); + setWorkspace(updatedWorkspace); + } catch (err) { + notifications.show({ + message: err?.response?.data?.message, + color: "red", + }); + } + }; + + return ( + + + + ); +} diff --git a/apps/client/src/ee/personal-space/queries/personal-space-query.ts b/apps/client/src/ee/personal-space/queries/personal-space-query.ts new file mode 100644 index 000000000..4de492b66 --- /dev/null +++ b/apps/client/src/ee/personal-space/queries/personal-space-query.ts @@ -0,0 +1,34 @@ +import { + useMutation, + useQuery, + useQueryClient, + UseQueryResult, +} from "@tanstack/react-query"; +import { ISpace } from "@/features/space/types/space.types"; +import { + createPersonalSpace, + getPersonalSpace, +} from "@/ee/personal-space/services/personal-space-service"; + +export function usePersonalSpaceQuery( + enabled: boolean, +): UseQueryResult { + return useQuery({ + queryKey: ["personal-space"], + queryFn: () => getPersonalSpace(), + enabled, + staleTime: 5 * 60 * 1000, + }); +} + +export function useCreatePersonalSpaceMutation() { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (data) => createPersonalSpace(data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["personal-space"] }); + queryClient.invalidateQueries({ queryKey: ["spaces"] }); + }, + }); +} diff --git a/apps/client/src/ee/personal-space/services/personal-space-service.ts b/apps/client/src/ee/personal-space/services/personal-space-service.ts new file mode 100644 index 000000000..67cb7e5ce --- /dev/null +++ b/apps/client/src/ee/personal-space/services/personal-space-service.ts @@ -0,0 +1,14 @@ +import api from "@/lib/api-client"; +import { ISpace } from "@/features/space/types/space.types"; + +export async function getPersonalSpace(): Promise { + const req = await api.post("/personal-space/info", {}); + return req.data; +} + +export async function createPersonalSpace(data: { + name?: string; +}): Promise { + const req = await api.post("/personal-space/create", data); + return req.data; +} diff --git a/apps/client/src/features/space/components/create-space-form.tsx b/apps/client/src/features/space/components/create-space-form.tsx index d5d26d57b..b156704a3 100644 --- a/apps/client/src/features/space/components/create-space-form.tsx +++ b/apps/client/src/features/space/components/create-space-form.tsx @@ -17,8 +17,8 @@ const formSchema = z.object({ .min(2) .max(100) .regex( - /^[a-zA-Z0-9]+$/, - "Space slug must be alphanumeric. No special characters", + /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/, + "Space slug must start with a letter or number and may contain hyphens and underscores", ), description: z.string().max(500), }); diff --git a/apps/client/src/features/space/components/edit-space-form.tsx b/apps/client/src/features/space/components/edit-space-form.tsx index fae8de11d..5abe4144c 100644 --- a/apps/client/src/features/space/components/edit-space-form.tsx +++ b/apps/client/src/features/space/components/edit-space-form.tsx @@ -15,8 +15,8 @@ const formSchema = z.object({ .min(2) .max(100) .regex( - /^[a-zA-Z0-9]+$/, - "Space slug must be alphanumeric. No special characters", + /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/, + "Space slug must start with a letter or number and may contain hyphens and underscores", ), }); diff --git a/apps/client/src/features/space/types/space.types.ts b/apps/client/src/features/space/types/space.types.ts index c856d88a8..6937b233c 100644 --- a/apps/client/src/features/space/types/space.types.ts +++ b/apps/client/src/features/space/types/space.types.ts @@ -24,6 +24,7 @@ export interface ISpace { description: string; logo?: string; slug: string; + isPersonal?: boolean; hostname: string; creatorId: string; createdAt: Date; diff --git a/apps/client/src/features/workspace/types/workspace.types.ts b/apps/client/src/features/workspace/types/workspace.types.ts index eda89dd56..eb927e789 100644 --- a/apps/client/src/features/workspace/types/workspace.types.ts +++ b/apps/client/src/features/workspace/types/workspace.types.ts @@ -28,6 +28,7 @@ export interface IWorkspace { trashRetentionDays?: number; restrictApiToAdmins?: boolean; allowMemberTemplates?: boolean; + allowPersonalSpaces?: boolean; isScimEnabled?: boolean; } @@ -36,6 +37,7 @@ export interface IWorkspaceSettings { sharing?: IWorkspaceSharingSettings; api?: IWorkspaceApiSettings; templates?: IWorkspaceTemplateSettings; + spaces?: IWorkspaceSpaceSettings; } export interface IWorkspaceApiSettings { @@ -57,6 +59,10 @@ export interface IWorkspaceTemplateSettings { allowMemberTemplates?: boolean; } +export interface IWorkspaceSpaceSettings { + allowPersonal?: boolean; +} + export interface ICreateInvite { role: string; emails: string[]; diff --git a/apps/client/src/pages/settings/workspace/workspace-settings.tsx b/apps/client/src/pages/settings/workspace/workspace-settings.tsx index 29e2841ca..e31b41b0f 100644 --- a/apps/client/src/pages/settings/workspace/workspace-settings.tsx +++ b/apps/client/src/pages/settings/workspace/workspace-settings.tsx @@ -7,6 +7,7 @@ import { Helmet } from "react-helmet-async"; import ManageHostname from "@/ee/components/manage-hostname.tsx"; import { Divider } from "@mantine/core"; import AllowMemberTemplates from "@/ee/security/components/allow-member-templates.tsx"; +import PersonalSpacesSetting from "@/ee/personal-space/components/personal-spaces-setting.tsx"; export default function WorkspaceSettings() { const { t } = useTranslation(); @@ -22,6 +23,9 @@ export default function WorkspaceSettings() { + + + {isCloud() && ( <> diff --git a/apps/client/src/styles/a11y-overrides.css b/apps/client/src/styles/a11y-overrides.css index 69727e079..25c1eafe7 100644 --- a/apps/client/src/styles/a11y-overrides.css +++ b/apps/client/src/styles/a11y-overrides.css @@ -25,3 +25,11 @@ .mantine-Input-input[data-variant="default"] { border-color: var(--mantine-color-gray-6); } + +/* better contrast for disabled inputs */ +.mantine-Input-input:disabled, +.mantine-Input-input[data-disabled] { + opacity: 0.7; + color: var(--mantine-color-dimmed); + -webkit-text-fill-color: var(--mantine-color-dimmed); +} diff --git a/apps/server/src/common/features.ts b/apps/server/src/common/features.ts index 4e2723943..0c0218d1e 100644 --- a/apps/server/src/common/features.ts +++ b/apps/server/src/common/features.ts @@ -20,6 +20,7 @@ export const Feature = { VIEWER_COMMENTS: 'comment:viewer', TEMPLATES: 'templates', PDF_EXPORT: 'export:pdf', + PERSONAL_SPACES: 'spaces:personal', DOCX_EXPORT: 'export:docx', } as const; diff --git a/apps/server/src/core/space/dto/create-space.dto.ts b/apps/server/src/core/space/dto/create-space.dto.ts index 310bdcf25..81af5b3fa 100644 --- a/apps/server/src/core/space/dto/create-space.dto.ts +++ b/apps/server/src/core/space/dto/create-space.dto.ts @@ -1,7 +1,7 @@ import { - IsAlphanumeric, IsOptional, IsString, + Matches, MaxLength, MinLength, } from 'class-validator'; @@ -20,6 +20,9 @@ export class CreateSpaceDto { @MinLength(2) @MaxLength(100) - @IsAlphanumeric() + @Matches(/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/, { + message: + 'Space slug must start with a letter or number and may contain hyphens and underscores', + }) slug: string; } diff --git a/apps/server/src/core/space/services/space.service.ts b/apps/server/src/core/space/services/space.service.ts index 2675a9e6a..af1a61079 100644 --- a/apps/server/src/core/space/services/space.service.ts +++ b/apps/server/src/core/space/services/space.service.ts @@ -48,6 +48,7 @@ export class SpaceService { workspaceId: string, createSpaceDto: CreateSpaceDto, trx?: KyselyTransaction, + options?: { isPersonal?: boolean }, ): Promise { let space = null; @@ -59,6 +60,7 @@ export class SpaceService { workspaceId, createSpaceDto, trx, + options, ); await this.spaceMemberService.addUserToSpace( @@ -81,6 +83,7 @@ export class SpaceService { after: { name: space.name, slug: space.slug, + ...(space.isPersonal ? { isPersonal: true } : {}), }, }, }); @@ -93,6 +96,7 @@ export class SpaceService { workspaceId: string, createSpaceDto: CreateSpaceDto, trx?: KyselyTransaction, + options?: { isPersonal?: boolean }, ): Promise { const slugExists = await this.spaceRepo.slugExists( createSpaceDto.slug, @@ -112,6 +116,7 @@ export class SpaceService { creatorId: userId, workspaceId: workspaceId, slug: createSpaceDto.slug, + isPersonal: options?.isPersonal ?? false, }, trx, ); diff --git a/apps/server/src/core/workspace/dto/update-workspace.dto.ts b/apps/server/src/core/workspace/dto/update-workspace.dto.ts index 25697a4b9..4d0a650b3 100644 --- a/apps/server/src/core/workspace/dto/update-workspace.dto.ts +++ b/apps/server/src/core/workspace/dto/update-workspace.dto.ts @@ -57,4 +57,8 @@ export class UpdateWorkspaceDto extends PartialType(CreateWorkspaceDto) { @IsOptional() @IsBoolean() allowMemberTemplates: boolean; + + @IsOptional() + @IsBoolean() + allowPersonalSpaces: boolean; } diff --git a/apps/server/src/core/workspace/services/workspace.service.ts b/apps/server/src/core/workspace/services/workspace.service.ts index f3ab78e60..8f5e3c993 100644 --- a/apps/server/src/core/workspace/services/workspace.service.ts +++ b/apps/server/src/core/workspace/services/workspace.service.ts @@ -333,7 +333,8 @@ export class WorkspaceService { typeof updateWorkspaceDto.mcpEnabled !== 'undefined' || typeof updateWorkspaceDto.restrictApiToAdmins !== 'undefined' || typeof updateWorkspaceDto.allowMemberTemplates !== 'undefined' || - typeof updateWorkspaceDto.isScimEnabled !== 'undefined' + typeof updateWorkspaceDto.isScimEnabled !== 'undefined' || + typeof updateWorkspaceDto.allowPersonalSpaces !== 'undefined' ) { const ws = await this.db .selectFrom('workspaces') @@ -361,6 +362,18 @@ export class WorkspaceService { } } + if (typeof updateWorkspaceDto.allowPersonalSpaces !== 'undefined') { + if ( + !this.licenseCheckService.hasFeature( + ws.licenseKey, + Feature.PERSONAL_SPACES, + ws.plan, + ) + ) { + throw new ForbiddenException('This feature requires a valid license'); + } + } + if ( typeof updateWorkspaceDto.disablePublicSharing !== 'undefined' || typeof updateWorkspaceDto.trashRetentionDays !== 'undefined' || @@ -500,6 +513,20 @@ export class WorkspaceService { ); } + if (typeof updateWorkspaceDto.allowPersonalSpaces !== 'undefined') { + const prev = settingsBefore?.spaces?.allowPersonal ?? false; + if (prev !== updateWorkspaceDto.allowPersonalSpaces) { + before.allowPersonalSpaces = prev; + after.allowPersonalSpaces = updateWorkspaceDto.allowPersonalSpaces; + } + await this.workspaceRepo.updateSpaceSettings( + workspaceId, + 'allowPersonal', + updateWorkspaceDto.allowPersonalSpaces, + trx, + ); + } + delete updateWorkspaceDto.restrictApiToAdmins; delete updateWorkspaceDto.aiSearch; delete updateWorkspaceDto.generativeAi; @@ -507,6 +534,7 @@ export class WorkspaceService { delete updateWorkspaceDto.mcpEnabled; delete updateWorkspaceDto.allowMemberTemplates; delete updateWorkspaceDto.aiChat; + delete updateWorkspaceDto.allowPersonalSpaces; await this.workspaceRepo.updateWorkspace( updateWorkspaceDto, diff --git a/apps/server/src/database/migrations/20260620T010047-personal-spaces.ts b/apps/server/src/database/migrations/20260620T010047-personal-spaces.ts new file mode 100644 index 000000000..bade5e3ae --- /dev/null +++ b/apps/server/src/database/migrations/20260620T010047-personal-spaces.ts @@ -0,0 +1,24 @@ +import { Kysely, sql } from 'kysely'; + +export async function up(db: Kysely): Promise { + await db.schema + .alterTable('spaces') + .addColumn('is_personal', 'boolean', (col) => + col.notNull().defaultTo(false), + ) + .execute(); + + await sql` + CREATE UNIQUE INDEX spaces_personal_creator_unique + ON spaces (creator_id) + WHERE is_personal = true AND deleted_at IS NULL + `.execute(db); +} + +export async function down(db: Kysely): Promise { + await db.schema + .dropIndex('spaces_personal_creator_unique') + .ifExists() + .execute(); + await db.schema.alterTable('spaces').dropColumn('is_personal').execute(); +} diff --git a/apps/server/src/database/repos/space/space.repo.ts b/apps/server/src/database/repos/space/space.repo.ts index 0b3896650..905d2fac6 100644 --- a/apps/server/src/database/repos/space/space.repo.ts +++ b/apps/server/src/database/repos/space/space.repo.ts @@ -57,6 +57,22 @@ export class SpaceRepo { .executeTakeFirst(); } + async findPersonalSpace( + userId: string, + workspaceId: string, + trx?: KyselyTransaction, + ): Promise { + const db = dbOrTx(this.db, trx); + return db + .selectFrom('spaces') + .selectAll('spaces') + .where('workspaceId', '=', workspaceId) + .where('creatorId', '=', userId) + .where('isPersonal', '=', true) + .where('deletedAt', 'is', null) + .executeTakeFirst(); + } + async slugExists( slug: string, workspaceId: string, diff --git a/apps/server/src/database/repos/workspace/workspace.repo.ts b/apps/server/src/database/repos/workspace/workspace.repo.ts index 408c46ba9..6bf2d447a 100644 --- a/apps/server/src/database/repos/workspace/workspace.repo.ts +++ b/apps/server/src/database/repos/workspace/workspace.repo.ts @@ -251,4 +251,24 @@ export class WorkspaceRepo { .executeTakeFirst(); } + async updateSpaceSettings( + workspaceId: string, + prefKey: string, + prefValue: string | boolean, + trx?: KyselyTransaction, + ) { + const db = dbOrTx(this.db, trx); + return db + .updateTable('workspaces') + .set({ + settings: sql`COALESCE(settings, '{}'::jsonb) + || jsonb_build_object('spaces', COALESCE(settings->'spaces', '{}'::jsonb) + || jsonb_build_object('${sql.raw(prefKey)}', ${sql.lit(prefValue)}))`, + updatedAt: new Date(), + }) + .where('id', '=', workspaceId) + .returning(this.baseFields) + .executeTakeFirst(); + } + } diff --git a/apps/server/src/database/types/db.d.ts b/apps/server/src/database/types/db.d.ts index 4463aa7aa..b0f875cf7 100644 --- a/apps/server/src/database/types/db.d.ts +++ b/apps/server/src/database/types/db.d.ts @@ -322,6 +322,7 @@ export interface Spaces { deletedAt: Timestamp | null; description: string | null; id: Generated; + isPersonal: Generated; logo: string | null; name: string | null; settings: Json | null; diff --git a/apps/server/src/ee b/apps/server/src/ee index 7afa4e9f2..efd4d10df 160000 --- a/apps/server/src/ee +++ b/apps/server/src/ee @@ -1 +1 @@ -Subproject commit 7afa4e9f2bafa8aa99be83f5e76e75d10cf9cde4 +Subproject commit efd4d10dfd83d6930c2f069dd934d57314c32576