diff --git a/src/app/_whiteboards/components/whiteboard.tsx b/src/app/_whiteboards/components/whiteboard.tsx index 2891d98..3b0f0c2 100644 --- a/src/app/_whiteboards/components/whiteboard.tsx +++ b/src/app/_whiteboards/components/whiteboard.tsx @@ -11,6 +11,8 @@ import { type ExcalidrawInitialDataState } from '@excalidraw/excalidraw/types/types' +import { decompressContent } from '@/lib/compress-whiteboard' + export interface Content { scene: { @@ -31,6 +33,13 @@ interface Props { onChange?: WhiteboardChangeEventHandler } +interface RawContentProps extends Omit { + id: number + viewModeEnabled?: boolean + initialRawCompressed?: string + onChange?: WhiteboardChangeEventHandler +} + const Excalidraw = dynamic( async () => (await import('@excalidraw/excalidraw')).Excalidraw, { @@ -96,4 +105,21 @@ export const Whiteboard = ({ ) } - \ No newline at end of file + + +export const WhiterboardFromCompressed = ({ initialRawCompressed, + ...rest +}: RawContentProps) => { + + const [initialContent, setInitialContent] = useState() + + useEffect(() => { + if (!initialRawCompressed){ + return + } + void decompressContent(initialRawCompressed) + .then((content) => setInitialContent(content as unknown as Content)) + }, [initialRawCompressed]) + + return +} \ No newline at end of file diff --git a/src/app/_whiteboards/hooks/use-whiteboard.ts b/src/app/_whiteboards/hooks/use-whiteboard.ts index 370234a..2921e58 100644 --- a/src/app/_whiteboards/hooks/use-whiteboard.ts +++ b/src/app/_whiteboards/hooks/use-whiteboard.ts @@ -7,8 +7,7 @@ import { type ExcalidrawElement } from '@excalidraw/excalidraw/types/element/typ import { type AppState, type BinaryFiles } from '@excalidraw/excalidraw/types/types' import { useDebounceCallback } from '@/app/_shared/hooks/use-debounce-callback' -import { b64encode } from '@/lib/base64' -import { compressStream, JSONtoStream, responseToBuffer } from '@/lib/compress' +import { compressContent } from '@/lib/compress-whiteboard' import * as exportUtils from '@/lib/export-whiteboard' import { api } from '@/trpc/react' @@ -106,9 +105,7 @@ export const useWhiteboard = (id: number) => { } } - void compressStream(JSONtoStream(content)) - .then(responseToBuffer) - .then(b64encode) + void compressContent(content) .then((compressedRawContent) => updateContent({ id: currentWhiteboard.id, compressedRawContent diff --git a/src/app/view-whiteboard/[id]/layout.tsx b/src/app/view-whiteboard/[id]/layout.tsx index a9a3b8e..658c028 100644 --- a/src/app/view-whiteboard/[id]/layout.tsx +++ b/src/app/view-whiteboard/[id]/layout.tsx @@ -3,14 +3,14 @@ import { type Metadata,type ResolvingMetadata } from 'next' import { redirect } from 'next/navigation' import Loading from '@/app/(protected)/loading' -import findPublicWhiteboardById from '@/server/api/whiteboard/usecases/find-public-whiteboard' +import findWhiteboardInfo from '@/server/api/whiteboard/usecases/find-whiteboard-info' import { db } from '@/server/db' export async function generateMetadata({ params }: {params: {id: string}}, parent: ResolvingMetadata) { const id = Number(params.id) - const whiteboard = await findPublicWhiteboardById(db, { id, omitContent: true }) + const whiteboard = await findWhiteboardInfo(db, { id, isPublic: true }) if (!whiteboard){ diff --git a/src/app/view-whiteboard/[id]/page.tsx b/src/app/view-whiteboard/[id]/page.tsx index ee5a990..43f325d 100644 --- a/src/app/view-whiteboard/[id]/page.tsx +++ b/src/app/view-whiteboard/[id]/page.tsx @@ -3,21 +3,20 @@ import React from 'react' import { redirect } from 'next/navigation' -import { type Content,Whiteboard } from '@/app/_whiteboards/components/whiteboard' +import { WhiterboardFromCompressed } from '@/app/_whiteboards/components/whiteboard' import { WhiteboardHeader } from '@/app/_whiteboards/components/whiteboard-header' -import { type PublicWhiteboard } from '@/app/_whiteboards/interfaces/whiteboard' -import findPublicWhiteboardById from '@/server/api/whiteboard/usecases/find-public-whiteboard' +import findWhiteboardContent from '@/server/api/whiteboard/usecases/find-whiteboard-content' import { db } from '@/server/db' const getWhiteboard = async (id: number) => { - const whiteboard = await findPublicWhiteboardById(db, { id }) + const whiteboard = await findWhiteboardContent(db, { id, isPublic: true }) if (!whiteboard){ redirect('/not-found') } - return whiteboard as unknown as PublicWhiteboard & {content: undefined | Content} + return whiteboard } @@ -25,21 +24,21 @@ const getWhiteboard = async (id: number) => { const WhitebardViewPage = async ({ params }: {params: {id: string}}) => { const whiteboardId = Number(params.id) - const whiteboard: PublicWhiteboard = await getWhiteboard(whiteboardId) + const whiteboard = await getWhiteboard(whiteboardId) return (
-
diff --git a/src/dtos/whiteboard-dtos.ts b/src/dtos/whiteboard-dtos.ts index 521f71a..6627829 100644 --- a/src/dtos/whiteboard-dtos.ts +++ b/src/dtos/whiteboard-dtos.ts @@ -1,5 +1,7 @@ import { z } from 'zod' +import { SearchByIdDto } from './shared-dtos' + export const CreateWhiteboardDto = z.object({ name: z.string().min(1, 'A name is required').max(30, 'The name must be a maximum of 30 characters.'), description: z.string().max(180, 'The description must be a maximum of 180 characters.').optional(), @@ -15,3 +17,8 @@ export const UpdateWhiteboardContentDto = z.object({ id: z.number(), compressedRawContent: z.string(), }) + + +export const SearchWhitheboard = z.object({ + isPublic: z.boolean().optional(), +}).merge(SearchByIdDto) \ No newline at end of file diff --git a/src/lib/base64.ts b/src/lib/base64.ts index 6f30def..ee8a5a1 100644 --- a/src/lib/base64.ts +++ b/src/lib/base64.ts @@ -1,32 +1,12 @@ +export const b64encode = (arrayBuffer: ArrayBuffer) => { + const buffer = Buffer.from(arrayBuffer) + const base64String = buffer.toString('base64') -// export const b64encode = (buf: ArrayBuffer) => { -// return btoa(String.fromCharCode(...new Uint8Array(buf))) -// } - -export const b64encode = (buffer: ArrayBuffer) => { - let binary = '' - const bytes = new Uint8Array(buffer) - const len = bytes.byteLength - for (let i = 0; i < len; i++) { - binary += String.fromCharCode(bytes[i]!) - } - - return window.btoa(binary) + return base64String } - -// export const b64decode = (str: string) => { -// const binary_string = window.atob(str) -// const len = binary_string.length -// const bytes = new Uint8Array(new ArrayBuffer(len)) -// for (let i = 0; i < len; i++) { -// bytes[i] = binary_string.charCodeAt(i) -// } - -// return bytes -// } export const b64decode = (base64: string) => { const buffer = Buffer.from(base64, 'base64') return new Uint8Array(buffer) -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/lib/compress-whiteboard.ts b/src/lib/compress-whiteboard.ts new file mode 100644 index 0000000..a5224d0 --- /dev/null +++ b/src/lib/compress-whiteboard.ts @@ -0,0 +1,21 @@ +import { b64encode } from './base64' +import { + b64toStream, + compressStream, + decompressStream, + JSONtoStream, + responseToBuffer +} from './compress' + +export const compressContent = async (content: Record) => { + const compressedStream = await compressStream(JSONtoStream(content)) + const compressedBuffer = await responseToBuffer(compressedStream) + + return b64encode(compressedBuffer) +} + +export const decompressContent = (base64: string): Promise> => { + const contentResponse = decompressStream(b64toStream(base64)) + + return contentResponse.json() as Promise> +} \ No newline at end of file diff --git a/src/server/api/whiteboard/usecases/find-public-whiteboard.ts b/src/server/api/whiteboard/usecases/find-public-whiteboard.ts deleted file mode 100644 index 41b44f6..0000000 --- a/src/server/api/whiteboard/usecases/find-public-whiteboard.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { type PostgresJsDatabase } from 'drizzle-orm/postgres-js' -import { type z } from 'zod' - -import { type SearchByIdDto } from '@/dtos/shared-dtos' -import type * as schema from '@/server/db/schema' -import { NotFound } from '@/server/exceptions/not-found' - - -type Options = z.infer & {omitContent?: boolean} - -const findPublicWhiteboardById = async (db: PostgresJsDatabase, options: Options) => { - const { id, omitContent = false } = options - - const currentWhiteboard = await db.query.whiteboards.findFirst({ - where: (whiteboards, { eq, and }) => and(eq(whiteboards.id, id), eq(whiteboards.isPublic, true)), - columns: { - id: true, - name: true, - description: true, - content: !omitContent ? true : false, - createdAt: true, - updatedAt: true, - previewUrl: true, - }, - with: { - createdBy: { - columns: { - email: false, - emailVerified: false, - } - }, - } - }) - - if (!currentWhiteboard){ - throw new NotFound('Whiteboard not found') - } - - return currentWhiteboard -} - -export default findPublicWhiteboardById \ No newline at end of file diff --git a/src/server/api/whiteboard/usecases/find-whiteboard-content.ts b/src/server/api/whiteboard/usecases/find-whiteboard-content.ts new file mode 100644 index 0000000..f97030b --- /dev/null +++ b/src/server/api/whiteboard/usecases/find-whiteboard-content.ts @@ -0,0 +1,51 @@ +import { and, eq,type SQL } from 'drizzle-orm' +import { type PostgresJsDatabase } from 'drizzle-orm/postgres-js' +import { type z } from 'zod' + +import { type SearchWhitheboard } from '@/dtos/whiteboard-dtos' +import { compressContent } from '@/lib/compress-whiteboard' +import type * as schema from '@/server/db/schema' +import { whiteboards } from '@/server/db/schema' +import { NotFound } from '@/server/exceptions/not-found' + + +type Options = z.infer + +const findWhiteboardContent = async (db: PostgresJsDatabase, options: Options) => { + const { id, isPublic } = options + + let baseFilter = eq(whiteboards.id, id) + + if (isPublic !== undefined){ + baseFilter = and(baseFilter, eq(whiteboards.isPublic, isPublic)) as SQL + } + + const currentWhiteboard = await db.query.whiteboards.findFirst({ + where: baseFilter, + with: { + createdBy: { + columns: { + email: false, + emailVerified: false, + } + }, + } + }) + + if (!currentWhiteboard){ + throw new NotFound('Whiteboard not found') + } + + const compressedRawContent = await compressContent(currentWhiteboard.content as Record) + + return { + id: currentWhiteboard.id, + name: currentWhiteboard.name, + compressedRawContent, + description: currentWhiteboard.description, + previewUrl: currentWhiteboard.previewUrl, + createdBy: currentWhiteboard.createdBy, + } +} + +export default findWhiteboardContent \ No newline at end of file diff --git a/src/server/api/whiteboard/usecases/find-whiteboard-info.ts b/src/server/api/whiteboard/usecases/find-whiteboard-info.ts new file mode 100644 index 0000000..3b21047 --- /dev/null +++ b/src/server/api/whiteboard/usecases/find-whiteboard-info.ts @@ -0,0 +1,37 @@ +import { and, eq,type SQL } from 'drizzle-orm' +import { type PostgresJsDatabase } from 'drizzle-orm/postgres-js' +import { type z } from 'zod' + +import { type SearchWhitheboard } from '@/dtos/whiteboard-dtos' +import type * as schema from '@/server/db/schema' +import { whiteboards } from '@/server/db/schema' +import { NotFound } from '@/server/exceptions/not-found' + + +type Options = z.infer + +const findWhiteboardInfo = async (db: PostgresJsDatabase, options: Options) => { + const { id, isPublic } = options + + let baseFilter = eq(whiteboards.id, id) + + if (isPublic !== undefined){ + baseFilter = and(baseFilter, eq(whiteboards.isPublic, isPublic)) as SQL + } + + const currentWhiteboard = await db.query.whiteboards.findFirst({ + where: baseFilter, + columns: { + content: false, + } + }) + + if (!currentWhiteboard){ + throw new NotFound('Whiteboard not found') + } + + + return currentWhiteboard +} + +export default findWhiteboardInfo \ No newline at end of file diff --git a/src/server/api/whiteboard/usecases/update-whiteboard-content.ts b/src/server/api/whiteboard/usecases/update-whiteboard-content.ts index ca1a8cf..1fffcf7 100644 --- a/src/server/api/whiteboard/usecases/update-whiteboard-content.ts +++ b/src/server/api/whiteboard/usecases/update-whiteboard-content.ts @@ -3,7 +3,7 @@ import { type PostgresJsDatabase } from 'drizzle-orm/postgres-js' import { type z } from 'zod' import { type UpdateWhiteboardContentDto } from '@/dtos/whiteboard-dtos' -import { b64toStream, decompressStream } from '@/lib/compress' +import { decompressContent } from '@/lib/compress-whiteboard' import type * as schema from '@/server/db/schema' import { whiteboards } from '@/server/db/schema' import { NotAuthorized } from '@/server/exceptions/not-authorized' @@ -29,9 +29,7 @@ const updateWhiteboardContent = async (db: PostgresJsDatabase, op if (!isOwner){ throw new NotAuthorized('User not related to whiteboard') } - - const contentResponse = decompressStream(b64toStream(compressedRawContent)) - const content = await contentResponse.json() as Record + const content = await decompressContent(compressedRawContent) return db.update(whiteboards).set({ content