mirror of
https://github.com/docmost/docmost.git
synced 2025-11-17 08:21:09 +10:00
Share - WIP
This commit is contained in:
@ -26,6 +26,7 @@ import { useTranslation } from "react-i18next";
|
||||
import Security from "@/ee/security/pages/security.tsx";
|
||||
import License from "@/ee/licence/pages/license.tsx";
|
||||
import { useRedirectToCloudSelect } from "@/ee/hooks/use-redirect-to-cloud-select.tsx";
|
||||
import SharedPage from '@/pages/share/shared-page.tsx';
|
||||
|
||||
export default function App() {
|
||||
const { t } = useTranslation();
|
||||
@ -51,6 +52,8 @@ export default function App() {
|
||||
</>
|
||||
)}
|
||||
|
||||
<Route path={"/share/:shareId/:pageId"} element={<SharedPage />} />
|
||||
|
||||
<Route path={"/p/:pageSlug"} element={<PageRedirect />} />
|
||||
|
||||
<Route element={<Layout />}>
|
||||
|
||||
@ -139,7 +139,7 @@ export default function DrawioView(props: NodeViewProps) {
|
||||
)}
|
||||
/>
|
||||
|
||||
{selected && (
|
||||
{selected && editor.isEditable && (
|
||||
<ActionIcon
|
||||
onClick={handleOpen}
|
||||
variant="default"
|
||||
|
||||
@ -170,7 +170,7 @@ export default function ExcalidrawView(props: NodeViewProps) {
|
||||
)}
|
||||
/>
|
||||
|
||||
{selected && (
|
||||
{selected && editor.isEditable && (
|
||||
<ActionIcon
|
||||
onClick={handleOpen}
|
||||
variant="default"
|
||||
|
||||
52
apps/client/src/features/editor/readonly-page-editor.tsx
Normal file
52
apps/client/src/features/editor/readonly-page-editor.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import "@/features/editor/styles/index.css";
|
||||
import React, { useMemo } from "react";
|
||||
import { EditorProvider } from "@tiptap/react";
|
||||
import { mainExtensions } from "@/features/editor/extensions/extensions";
|
||||
import { Document } from "@tiptap/extension-document";
|
||||
import { Heading } from "@tiptap/extension-heading";
|
||||
import { Text } from "@tiptap/extension-text";
|
||||
import { Placeholder } from "@tiptap/extension-placeholder";
|
||||
|
||||
interface PageEditorProps {
|
||||
title: string;
|
||||
content: any;
|
||||
}
|
||||
|
||||
export default function ReadonlyPageEditor({
|
||||
title,
|
||||
content,
|
||||
}: PageEditorProps) {
|
||||
const extensions = useMemo(() => {
|
||||
return [...mainExtensions];
|
||||
}, []);
|
||||
|
||||
const titleExtensions = [
|
||||
Document.extend({
|
||||
content: "heading",
|
||||
}),
|
||||
Heading,
|
||||
Text,
|
||||
Placeholder.configure({
|
||||
placeholder: "Untitled",
|
||||
showOnlyWhenEditable: false,
|
||||
}),
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<EditorProvider
|
||||
editable={false}
|
||||
immediatelyRender={true}
|
||||
extensions={titleExtensions}
|
||||
content={title}
|
||||
></EditorProvider>
|
||||
|
||||
<EditorProvider
|
||||
editable={false}
|
||||
immediatelyRender={true}
|
||||
extensions={extensions}
|
||||
content={content}
|
||||
></EditorProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
|
||||
const buildPageSlug = (pageSlugId: string, pageTitle?: string): string => {
|
||||
export const buildPageSlug = (pageSlugId: string, pageTitle?: string): string => {
|
||||
const titleSlug = slugify(pageTitle?.substring(0, 70) || "untitled", {
|
||||
customReplacements: [
|
||||
["♥", ""],
|
||||
@ -8,7 +8,7 @@ const buildPageSlug = (pageSlugId: string, pageTitle?: string): string => {
|
||||
],
|
||||
});
|
||||
|
||||
return `p/${titleSlug}-${pageSlugId}`;
|
||||
return `${titleSlug}-${pageSlugId}`;
|
||||
};
|
||||
|
||||
export const buildPageUrl = (
|
||||
@ -17,7 +17,7 @@ export const buildPageUrl = (
|
||||
pageTitle?: string,
|
||||
): string => {
|
||||
if (spaceName === undefined) {
|
||||
return `/${buildPageSlug(pageSlugId, pageTitle)}`;
|
||||
return `/p/${buildPageSlug(pageSlugId, pageTitle)}`;
|
||||
}
|
||||
return `/s/${spaceName}/${buildPageSlug(pageSlugId, pageTitle)}`;
|
||||
return `/s/${spaceName}/p/${buildPageSlug(pageSlugId, pageTitle)}`;
|
||||
};
|
||||
|
||||
65
apps/client/src/features/share/queries/share-query.ts
Normal file
65
apps/client/src/features/share/queries/share-query.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import {
|
||||
useMutation,
|
||||
useQuery,
|
||||
UseQueryResult,
|
||||
} from "@tanstack/react-query";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { validate as isValidUuid } from "uuid";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
ICreateShare,
|
||||
IShareInput,
|
||||
} from "@/features/share/types/share.types.ts";
|
||||
import {
|
||||
createShare,
|
||||
deleteShare,
|
||||
getShare,
|
||||
updateShare,
|
||||
} from "@/features/share/services/share-service.ts";
|
||||
import { IPage } from "@/features/page/types/page.types.ts";
|
||||
|
||||
export function useShareQuery(
|
||||
shareInput: Partial<IShareInput>,
|
||||
): UseQueryResult<IPage, Error> {
|
||||
const query = useQuery({
|
||||
queryKey: ["shares", shareInput],
|
||||
queryFn: () => getShare(shareInput),
|
||||
enabled: !!shareInput.shareId,
|
||||
staleTime: 5 * 60 * 1000,
|
||||
});
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
export function useCreateShareMutation() {
|
||||
const { t } = useTranslation();
|
||||
return useMutation<any, Error, ICreateShare>({
|
||||
mutationFn: (data) => createShare(data),
|
||||
onSuccess: (data) => {},
|
||||
onError: (error) => {
|
||||
notifications.show({ message: t("Failed to share page"), color: "red" });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateShareMutation() {
|
||||
return useMutation<any, Error, Partial<IShareInput>>({
|
||||
mutationFn: (data) => updateShare(data),
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteShareMutation() {
|
||||
const { t } = useTranslation();
|
||||
return useMutation({
|
||||
mutationFn: (shareId: string) => deleteShare(shareId),
|
||||
onSuccess: () => {
|
||||
notifications.show({ message: t("Share deleted successfully") });
|
||||
},
|
||||
onError: (error) => {
|
||||
notifications.show({
|
||||
message: t("Failed to delete share"),
|
||||
color: "red",
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
36
apps/client/src/features/share/services/share-service.ts
Normal file
36
apps/client/src/features/share/services/share-service.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import api from "@/lib/api-client";
|
||||
import {
|
||||
IExportPageParams,
|
||||
IMovePage,
|
||||
IMovePageToSpace,
|
||||
IPage,
|
||||
IPageInput,
|
||||
SidebarPagesParams,
|
||||
} from "@/features/page/types/page.types";
|
||||
import { IAttachment, IPagination } from "@/lib/types.ts";
|
||||
import { saveAs } from "file-saver";
|
||||
import {
|
||||
ICreateShare,
|
||||
IShareInput,
|
||||
} from "@/features/share/types/share.types.ts";
|
||||
|
||||
export async function createShare(data: ICreateShare): Promise<any> {
|
||||
const req = await api.post<any>("/shares/create", data);
|
||||
return req.data;
|
||||
}
|
||||
|
||||
export async function getShare(
|
||||
shareInput: Partial<IShareInput>,
|
||||
): Promise<IPage> {
|
||||
const req = await api.post<IPage>("/shares/info", shareInput);
|
||||
return req.data;
|
||||
}
|
||||
|
||||
export async function updateShare(data: Partial<IShareInput>): Promise<any> {
|
||||
const req = await api.post<IPage>("/shares/update", data);
|
||||
return req.data;
|
||||
}
|
||||
|
||||
export async function deleteShare(shareId: string): Promise<void> {
|
||||
await api.post("/shares/delete", { shareId });
|
||||
}
|
||||
12
apps/client/src/features/share/types/share.types.ts
Normal file
12
apps/client/src/features/share/types/share.types.ts
Normal file
@ -0,0 +1,12 @@
|
||||
|
||||
export interface ICreateShare {
|
||||
slugId: string;
|
||||
pageId: string;
|
||||
}
|
||||
|
||||
|
||||
export interface IShareInput {
|
||||
shareId: string;
|
||||
pageId?: string;
|
||||
}
|
||||
|
||||
56
apps/client/src/pages/share/shared-page.tsx
Normal file
56
apps/client/src/pages/share/shared-page.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useShareQuery } from "@/features/share/queries/share-query.ts";
|
||||
import { Container } from "@mantine/core";
|
||||
import React, { useEffect } from "react";
|
||||
import ReadonlyPageEditor from "@/features/editor/readonly-page-editor.tsx";
|
||||
import { buildPageSlug } from "@/features/page/page.utils.ts";
|
||||
|
||||
export default function SharedPage() {
|
||||
const { t } = useTranslation();
|
||||
const { shareId } = useParams();
|
||||
const {
|
||||
data: page,
|
||||
isLoading,
|
||||
isError,
|
||||
error,
|
||||
} = useShareQuery({ shareId: shareId });
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (!page) return;
|
||||
const pageSlug = buildPageSlug(page.slugId, page.title);
|
||||
const shareSlug = `/share/${shareId}/${pageSlug}`;
|
||||
navigate(shareSlug, { replace: true });
|
||||
}, [page]);
|
||||
|
||||
if (isLoading) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
if (isError || !page) {
|
||||
if ([401, 403, 404].includes(error?.["status"])) {
|
||||
return <div>{t("Page not found")}</div>;
|
||||
}
|
||||
return <div>{t("Error fetching page data.")}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
page && (
|
||||
<div>
|
||||
<Helmet>
|
||||
<title>{`${page?.icon || ""} ${page?.title || t("untitled")}`}</title>
|
||||
</Helmet>
|
||||
|
||||
<Container size={900} pt={50}>
|
||||
<ReadonlyPageEditor
|
||||
key={page.id}
|
||||
title={page.title}
|
||||
content={page.content}
|
||||
/>
|
||||
</Container>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -15,6 +15,7 @@ import { SpaceModule } from './space/space.module';
|
||||
import { GroupModule } from './group/group.module';
|
||||
import { CaslModule } from './casl/casl.module';
|
||||
import { DomainMiddleware } from '../common/middlewares/domain.middleware';
|
||||
import { ShareModule } from './share/share.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -28,6 +29,7 @@ import { DomainMiddleware } from '../common/middlewares/domain.middleware';
|
||||
SpaceModule,
|
||||
GroupModule,
|
||||
CaslModule,
|
||||
ShareModule,
|
||||
],
|
||||
})
|
||||
export class CoreModule implements NestModule {
|
||||
|
||||
6
apps/server/src/core/share/dto/create-share.dto.ts
Normal file
6
apps/server/src/core/share/dto/create-share.dto.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { IsString } from 'class-validator';
|
||||
|
||||
export class CreateShareDto {
|
||||
@IsString()
|
||||
pageId: string;
|
||||
}
|
||||
28
apps/server/src/core/share/dto/share.dto.ts
Normal file
28
apps/server/src/core/share/dto/share.dto.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import {
|
||||
IsBoolean,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
} from 'class-validator';
|
||||
|
||||
export class ShareIdDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
shareId: string;
|
||||
}
|
||||
|
||||
export class SpaceIdDto {
|
||||
@IsUUID()
|
||||
spaceId: string;
|
||||
}
|
||||
|
||||
export class ShareInfoDto extends ShareIdDto {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
pageId: string;
|
||||
|
||||
// @IsOptional()
|
||||
// @IsBoolean()
|
||||
// includeContent: boolean;
|
||||
}
|
||||
8
apps/server/src/core/share/dto/update-page.dto.ts
Normal file
8
apps/server/src/core/share/dto/update-page.dto.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { PartialType } from '@nestjs/mapped-types';
|
||||
import { CreateShareDto } from './create-share.dto';
|
||||
import { IsString } from 'class-validator';
|
||||
|
||||
export class UpdateShareDto extends PartialType(CreateShareDto) {
|
||||
//@IsString()
|
||||
//pageId: string;
|
||||
}
|
||||
105
apps/server/src/core/share/share.controller.ts
Normal file
105
apps/server/src/core/share/share.controller.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
ForbiddenException,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
NotFoundException,
|
||||
Post,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { AuthUser } from '../../common/decorators/auth-user.decorator';
|
||||
import { User, Workspace } from '@docmost/db/types/entity.types';
|
||||
import {
|
||||
SpaceCaslAction,
|
||||
SpaceCaslSubject,
|
||||
} from '../casl/interfaces/space-ability.type';
|
||||
import { AuthWorkspace } from '../../common/decorators/auth-workspace.decorator';
|
||||
import SpaceAbilityFactory from '../casl/abilities/space-ability.factory';
|
||||
import { ShareService } from './share.service';
|
||||
import { UpdateShareDto } from './dto/update-page.dto';
|
||||
import { CreateShareDto } from './dto/create-share.dto';
|
||||
import { ShareIdDto, ShareInfoDto } from './dto/share.dto';
|
||||
import { PageRepo } from '@docmost/db/repos/page/page.repo';
|
||||
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
||||
import { Public } from '../../common/decorators/public.decorator';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Controller('shares')
|
||||
export class ShareController {
|
||||
constructor(
|
||||
private readonly shareService: ShareService,
|
||||
private readonly spaceAbility: SpaceAbilityFactory,
|
||||
private readonly pageRepo: PageRepo,
|
||||
) {}
|
||||
|
||||
@Public()
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('/info')
|
||||
async getPage(@Body() dto: ShareInfoDto) {
|
||||
return this.shareService.getShare(dto);
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('create')
|
||||
async create(
|
||||
@Body() createShareDto: CreateShareDto,
|
||||
@AuthUser() user: User,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
) {
|
||||
|
||||
const page = await this.pageRepo.findById(createShareDto.pageId);
|
||||
|
||||
if (!page) {
|
||||
throw new NotFoundException('Page not found');
|
||||
}
|
||||
|
||||
const ability = await this.spaceAbility.createForUser(user, page.spaceId);
|
||||
if (ability.cannot(SpaceCaslAction.Create, SpaceCaslSubject.Page)) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
return this.shareService.createShare({
|
||||
pageId: page.id,
|
||||
authUserId: user.id,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('update')
|
||||
async update(@Body() updatePageDto: UpdateShareDto, @AuthUser() user: User) {
|
||||
/* const page = await this.pageRepo.findById(updatePageDto.pageId);
|
||||
|
||||
if (!page) {
|
||||
throw new NotFoundException('Page not found');
|
||||
}
|
||||
|
||||
const ability = await this.spaceAbility.createForUser(user, page.spaceId);
|
||||
if (ability.cannot(SpaceCaslAction.Edit, SpaceCaslSubject.Page)) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
//return this.shareService.update(page, updatePageDto, user.id);
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('delete')
|
||||
async delete(@Body() shareIdDto: ShareIdDto, @AuthUser() user: User) {
|
||||
/* const page = await this.pageRepo.findById(pageIdDto.pageId);
|
||||
|
||||
if (!page) {
|
||||
throw new NotFoundException('Page not found');
|
||||
}
|
||||
|
||||
const ability = await this.spaceAbility.createForUser(user, page.spaceId);
|
||||
if (ability.cannot(SpaceCaslAction.Manage, SpaceCaslSubject.Page)) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
*/
|
||||
// await this.shareService.forceDelete(pageIdDto.pageId);
|
||||
}
|
||||
}
|
||||
10
apps/server/src/core/share/share.module.ts
Normal file
10
apps/server/src/core/share/share.module.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ShareController } from './share.controller';
|
||||
import { ShareService } from './share.service';
|
||||
|
||||
@Module({
|
||||
controllers: [ShareController],
|
||||
providers: [ShareService],
|
||||
exports: [ShareService],
|
||||
})
|
||||
export class ShareModule {}
|
||||
65
apps/server/src/core/share/share.service.ts
Normal file
65
apps/server/src/core/share/share.service.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { ShareInfoDto } from './dto/share.dto';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { KyselyDB } from '@docmost/db/types/kysely.types';
|
||||
import { generateSlugId } from '../../common/helpers';
|
||||
import { PageRepo } from '@docmost/db/repos/page/page.repo';
|
||||
|
||||
@Injectable()
|
||||
export class ShareService {
|
||||
constructor(
|
||||
private readonly pageRepo: PageRepo,
|
||||
@InjectKysely() private readonly db: KyselyDB,
|
||||
) {}
|
||||
|
||||
async createShare(opts: {
|
||||
authUserId: string;
|
||||
workspaceId: string;
|
||||
pageId: string;
|
||||
}) {
|
||||
const { authUserId, workspaceId, pageId } = opts;
|
||||
|
||||
const slugId = generateSlugId(); // or custom slug
|
||||
const share = this.db
|
||||
.insertInto('shares')
|
||||
.values({ slugId: slugId, pageId, creatorId: authUserId, workspaceId })
|
||||
.returningAll()
|
||||
.executeTakeFirst();
|
||||
|
||||
return share;
|
||||
}
|
||||
|
||||
async getShare(dto: ShareInfoDto) {
|
||||
// for now only single page share
|
||||
|
||||
// if only share Id is provided, return
|
||||
|
||||
// if share id is pass with page id, what to do?
|
||||
// if uuid is used, use Id
|
||||
const share = await this.db
|
||||
.selectFrom('shares')
|
||||
.selectAll()
|
||||
.where('slugId', '=', dto.shareId)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!share) {
|
||||
throw new NotFoundException('Share not found');
|
||||
}
|
||||
|
||||
const page = await this.pageRepo.findById(share.pageId, {
|
||||
includeContent: true,
|
||||
includeCreator: true,
|
||||
});
|
||||
|
||||
// cleanup json content
|
||||
// remove comments mark
|
||||
// make sure attachments work (videos, images, excalidraw, drawio)
|
||||
// figure out internal links?
|
||||
|
||||
if (!page) {
|
||||
throw new NotFoundException('Page not found');
|
||||
}
|
||||
|
||||
return page;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
import { Kysely, sql } from 'kysely';
|
||||
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
await db.schema
|
||||
.createTable('shares')
|
||||
.addColumn('id', 'uuid', (col) =>
|
||||
col.primaryKey().defaultTo(sql`gen_uuid_v7()`),
|
||||
)
|
||||
.addColumn('slug_id', 'varchar', (col) => col.notNull())
|
||||
.addColumn('page_id', 'varchar', (col) => col.notNull())
|
||||
.addColumn('include_sub_pages', 'varchar', (col) => col)
|
||||
.addColumn('creator_id', 'uuid', (col) => col.references('users.id'))
|
||||
|
||||
// pageSlug
|
||||
|
||||
//.addColumn('space_id', 'uuid', (col) =>
|
||||
// col.references('spaces.id').onDelete('cascade').notNull(),
|
||||
// )
|
||||
.addColumn('workspace_id', 'uuid', (col) =>
|
||||
col.references('workspaces.id').onDelete('cascade').notNull(),
|
||||
)
|
||||
.addColumn('created_at', 'timestamptz', (col) =>
|
||||
col.notNull().defaultTo(sql`now()`),
|
||||
)
|
||||
.addColumn('updated_at', 'timestamptz', (col) =>
|
||||
col.notNull().defaultTo(sql`now()`),
|
||||
)
|
||||
.addColumn('deleted_at', 'timestamptz', (col) => col)
|
||||
.addUniqueConstraint('shares_slug_id_unique', ['slug_id'])
|
||||
.execute();
|
||||
|
||||
await db.schema
|
||||
.createIndex('shares_slug_id_idx')
|
||||
.on('shares')
|
||||
.column('slug_id')
|
||||
.execute();
|
||||
}
|
||||
|
||||
export async function down(db: Kysely<any>): Promise<void> {
|
||||
await db.schema.dropTable('shares').execute();
|
||||
}
|
||||
13
apps/server/src/database/types/db.d.ts
vendored
13
apps/server/src/database/types/db.d.ts
vendored
@ -183,6 +183,18 @@ export interface Pages {
|
||||
ydoc: Buffer | null;
|
||||
}
|
||||
|
||||
export interface Shares {
|
||||
createdAt: Generated<Timestamp>;
|
||||
creatorId: string | null;
|
||||
deletedAt: Timestamp | null;
|
||||
id: Generated<string>;
|
||||
includeSubPages: string | null;
|
||||
pageId: string;
|
||||
slugId: string;
|
||||
updatedAt: Generated<Timestamp>;
|
||||
workspaceId: string;
|
||||
}
|
||||
|
||||
export interface SpaceMembers {
|
||||
addedById: string | null;
|
||||
createdAt: Generated<Timestamp>;
|
||||
@ -288,6 +300,7 @@ export interface DB {
|
||||
groupUsers: GroupUsers;
|
||||
pageHistory: PageHistory;
|
||||
pages: Pages;
|
||||
shares: Shares;
|
||||
spaceMembers: SpaceMembers;
|
||||
spaces: Spaces;
|
||||
users: Users;
|
||||
|
||||
@ -16,6 +16,7 @@ import {
|
||||
Billing as BillingSubscription,
|
||||
AuthProviders,
|
||||
AuthAccounts,
|
||||
Shares,
|
||||
} from './db';
|
||||
|
||||
// Workspace
|
||||
@ -101,3 +102,8 @@ export type UpdatableAuthProvider = Updateable<Omit<AuthProviders, 'id'>>;
|
||||
export type AuthAccount = Selectable<AuthAccounts>;
|
||||
export type InsertableAuthAccount = Insertable<AuthAccounts>;
|
||||
export type UpdatableAuthAccount = Updateable<Omit<AuthAccounts, 'id'>>;
|
||||
|
||||
// Share
|
||||
export type Share = Selectable<Shares>;
|
||||
export type InsertableShare = Insertable<Shares>;
|
||||
export type UpdatableShare = Updateable<Omit<Shares, 'id'>>;
|
||||
|
||||
Reference in New Issue
Block a user