From d62c4c1e17baadb8d1c6542cc6795407664ff744 Mon Sep 17 00:00:00 2001 From: Sultan <105234327+natlusyht@users.noreply.github.com> Date: Sat, 25 Jun 2022 18:10:01 +0300 Subject: [PATCH 01/11] abstraction createContentController --- .../primitives/createContentController.js | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 app/shared/design-system/components/primitives/createContentController.js diff --git a/app/shared/design-system/components/primitives/createContentController.js b/app/shared/design-system/components/primitives/createContentController.js new file mode 100644 index 0000000000..3f99cbd6a7 --- /dev/null +++ b/app/shared/design-system/components/primitives/createContentController.js @@ -0,0 +1,93 @@ +const canvasContext = globalThis.document + ?.createElement("canvas") + .getContext("2d"); +const measureTextWidth = (value) => canvasContext.measureText(value).width; +const measureTextPadding = (node, value) => { + const { + fontSize, + fontFamily, + fontWeight, + textAlign, + letterSpacing, + paddingLeft, + paddingRight, + } = getComputedStyle(node); + if (letterSpacing !== "normal") canvasContext.letterSpacing = letterSpacing; + canvasContext.font = `${fontWeight} ${fontSize} ${fontFamily}`; + return textAlign === "left" + ? parseFloat(paddingLeft) + : node.clientWidth - + canvasContext.measureText(value).width - + parseFloat(paddingRight); +}; +const measureTextMetrics = (value, index, offset) => { + const DECIMAL_OR_WHITESPACE_OR_NON_WORD = /([-+]?\d*\.?\d+)|([\s\W])/; + return value + .split(DECIMAL_OR_WHITESPACE_OR_NON_WORD) + .filter((value) => value) + .map((value) => { + const width = measureTextWidth(value); + const head = index - offset; + const tail = index - (offset + width); + const props = { head, tail, value, width, length, offset }; + length += value.length; + offset += width; + return props; + }); +}; +export const createContentController = ( + targetNode, + { + read = (node) => node.value, + write = (value) => {}, + contents = [], + onMouseMove = () => {}, + onCaretMove = () => {}, + } +) => { + const eventNames = ["keyup", "pointermove"]; + const handleEvent = ({ type, offsetX }) => { + const targetValue = read(targetNode); + const targetPadding = measureTextPadding(targetNode, targetValue); + if (type === eventNames[0]) + offsetX = + targetPadding + + measureTextWidth(targetValue.slice(0, targetNode.selectionStart)); + const targetMetrics = measureTextMetrics( + targetValue, + offsetX, + targetPadding + ); + for (const targetMetricsData of targetMetrics) { + const { head, tail, value } = targetMetricsData; + // zero for exact boundary match, added buffer for precision forgiveness, more so from the head + if (head >= -2 && tail <= 0) { + const current = contents.find(({ match }) => match(value)); + if (current) { + const { name } = current; + const position = `${head},${tail}`; + if (type === eventNames[0]) { + onCaretMove({ name, value, position }); + } else if (type === eventNames[1]) { + onMouseMove({ name, value, position }); + } + } + } + } + }; + eventNames.forEach((eventName) => + targetNode.addEventListener(eventName, handleEvent) + ); + return { + update: (value) => { + let { selectionStart, selectionEnd } = targetNode; + write(targetNode, value); + targetNode.setSelectionRange?.(selectionStart, selectionEnd); + }, + disconnectedCallback: () => { + eventName.forEach((eventName) => + targetNode.addEventListener(eventName, handleEvent) + ); + }, + }; +}; From 15898c9ca0390c058aa07dd123a5f9455c9deaaa Mon Sep 17 00:00:00 2001 From: Sultan <105234327+natlusyht@users.noreply.github.com> Date: Sat, 25 Jun 2022 18:14:05 +0300 Subject: [PATCH 02/11] example jsdoc --- .../primitives/createContentController.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/shared/design-system/components/primitives/createContentController.js b/app/shared/design-system/components/primitives/createContentController.js index 3f99cbd6a7..e3893cb9cd 100644 --- a/app/shared/design-system/components/primitives/createContentController.js +++ b/app/shared/design-system/components/primitives/createContentController.js @@ -1,3 +1,16 @@ +/* + @example + + const {update, disconnectedCallback} = createContentController(document.querySelector('input'), { + contents: [ + { name: 'unit', match: (value) => value === 'px' }, + { name: 'number', match: (value) => isNaN(parseFloat(value)) === false }, + { name: 'unknown', match: (value) => true }, + ], + onMouseMove: ({name, value, position}) => { }, + onCaretMove: ({name, value, position}) => { }, + }); +*/ const canvasContext = globalThis.document ?.createElement("canvas") .getContext("2d"); From 7f057486e7778bfb253a1b74066cef12e6623723 Mon Sep 17 00:00:00 2001 From: Sultan <105234327+natlusyht@users.noreply.github.com> Date: Sun, 26 Jun 2022 00:23:53 +0300 Subject: [PATCH 03/11] polish jsdocs description and example nit on one call signature --- .../primitives/createContentController.js | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/app/shared/design-system/components/primitives/createContentController.js b/app/shared/design-system/components/primitives/createContentController.js index e3893cb9cd..c04679879a 100644 --- a/app/shared/design-system/components/primitives/createContentController.js +++ b/app/shared/design-system/components/primitives/createContentController.js @@ -1,16 +1,23 @@ -/* - @example - - const {update, disconnectedCallback} = createContentController(document.querySelector('input'), { - contents: [ - { name: 'unit', match: (value) => value === 'px' }, - { name: 'number', match: (value) => isNaN(parseFloat(value)) === false }, - { name: 'unknown', match: (value) => true }, - ], - onMouseMove: ({name, value, position}) => { }, - onCaretMove: ({name, value, position}) => { }, - }); -*/ +/** + * @description + * - detects whether the pointer/keyboard is on any given token as specified by the {contents: []} contract. + * - pointermove/keyup in turn dispatch onMouseMove/onCaretMove with the current state if and when within a configured content token. + * - returns two methods {update, disconnectedCallback} the former of which dispatches a content update(while preserving the caret position if any) + * - read/write methods are called when reading from the passed element or when updating/writing to the same element. + * @example + * const target = document.querySelector('input'); + * const {update, disconnectedCallback} = createContentController(target, { + * contents: [ + * { name: 'unit', match: (value) => value === 'px' }, + * { name: 'number', match: (value) => isNaN(parseFloat(value)) === false }, + * { name: 'unknown', match: (value) => true }, + * ], + * onMouseMove: ({name, value, position}) => { }, + * onCaretMove: ({name, value, position}) => { }, + * read: (value) => target.value, + * write: (value) => target.value = value, + * }); + */ const canvasContext = globalThis.document ?.createElement("canvas") .getContext("2d"); @@ -94,7 +101,7 @@ export const createContentController = ( return { update: (value) => { let { selectionStart, selectionEnd } = targetNode; - write(targetNode, value); + write(value); targetNode.setSelectionRange?.(selectionStart, selectionEnd); }, disconnectedCallback: () => { From 43b6ced2d84e275e06a49b013d77fa46cf691b4a Mon Sep 17 00:00:00 2001 From: Sultan <105234327+natlusyht@users.noreply.github.com> Date: Thu, 30 Jun 2022 22:35:18 +0300 Subject: [PATCH 04/11] createContentController stories,typescript --- .../createContentController.stories.tsx | 71 +++++++++++++++++ ...ntroller.js => createContentController.ts} | 76 +++++++++++++------ .../example-text-editor.stories.tsx | 2 +- 3 files changed, 125 insertions(+), 24 deletions(-) create mode 100644 app/shared/design-system/components/primitives/createContentController.stories.tsx rename app/shared/design-system/components/primitives/{createContentController.js => createContentController.ts} (60%) diff --git a/app/shared/design-system/components/primitives/createContentController.stories.tsx b/app/shared/design-system/components/primitives/createContentController.stories.tsx new file mode 100644 index 0000000000..091859f1a8 --- /dev/null +++ b/app/shared/design-system/components/primitives/createContentController.stories.tsx @@ -0,0 +1,71 @@ +import { useState, useRef, useEffect, RefObject } from "react"; +import { createContentController } from "./createContentController"; + +const useContentController = ({ + ref, + read, + write, + callback, +}: { + ref: RefObject; + read: (value: HTMLInputElement) => string; + write: (target: HTMLInputElement, value: string) => void; + callback: (value: { name: string; value: string }) => void; +}) => { + useEffect(() => { + const { disconnectedCallback } = createContentController(ref.current, { + contents: [ + { + name: "unit", + match: (value: string) => ["px", "em", "rem", "%"].includes(value), + }, + { + name: "number", + match: (value: string) => isNaN(parseFloat(value)) === false, + }, + { name: "unknown", match: (value: string) => value }, + ], + read, + write, + onMouseMove: callback, + onCaretMove: callback, + }); + return () => disconnectedCallback(); + }, [ref, read, write, callback]); +}; + +const ExampleContentController = () => { + const [content, setContent] = useState({ name: "none", value: "" }); + const ref = useRef(null); + useContentController({ + ref, + callback: (payload) => setContent(payload), + read: (target) => target.value, + write: (target, value) => (target.value = value), + }); + return ( +
+
+ +
+
+

Currently on: {content.name}

+

With a value of: {content.value}

+
+
+ ); +}; + +export const DetectInput = Object.assign(ExampleContentController.bind({}), { + args: {}, +}); + +export default { + title: "createContentController", + component: ExampleContentController, + argTypes: {}, +}; diff --git a/app/shared/design-system/components/primitives/createContentController.js b/app/shared/design-system/components/primitives/createContentController.ts similarity index 60% rename from app/shared/design-system/components/primitives/createContentController.js rename to app/shared/design-system/components/primitives/createContentController.ts index c04679879a..667c05c01b 100644 --- a/app/shared/design-system/components/primitives/createContentController.js +++ b/app/shared/design-system/components/primitives/createContentController.ts @@ -14,15 +14,16 @@ * ], * onMouseMove: ({name, value, position}) => { }, * onCaretMove: ({name, value, position}) => { }, - * read: (value) => target.value, - * write: (value) => target.value = value, + * read: (target) => target.value, + * write: (target, value) => target.value = value, * }); */ const canvasContext = globalThis.document - ?.createElement("canvas") - .getContext("2d"); -const measureTextWidth = (value) => canvasContext.measureText(value).width; -const measureTextPadding = (node, value) => { + .createElement("canvas") + .getContext("2d") as CanvasRenderingContext2D & { letterSpacing: string }; +const measureTextWidth = (value: string): number => + canvasContext.measureText(value).width; +const measureTextPadding = (node: HTMLElement, value: string) => { const { fontSize, fontFamily, @@ -34,13 +35,14 @@ const measureTextPadding = (node, value) => { } = getComputedStyle(node); if (letterSpacing !== "normal") canvasContext.letterSpacing = letterSpacing; canvasContext.font = `${fontWeight} ${fontSize} ${fontFamily}`; - return textAlign === "left" + return ["left", "start"].includes(textAlign) ? parseFloat(paddingLeft) : node.clientWidth - canvasContext.measureText(value).width - parseFloat(paddingRight); }; -const measureTextMetrics = (value, index, offset) => { +const measureTextMetrics = (value: string, index: number, offset: number) => { + let length = 0; const DECIMAL_OR_WHITESPACE_OR_NON_WORD = /([-+]?\d*\.?\d+)|([\s\W])/; return value .split(DECIMAL_OR_WHITESPACE_OR_NON_WORD) @@ -56,23 +58,50 @@ const measureTextMetrics = (value, index, offset) => { }); }; export const createContentController = ( - targetNode, + targetNode: HTMLInputElement, { - read = (node) => node.value, - write = (value) => {}, + read = (node: HTMLInputElement) => node.value, + write = (node: HTMLInputElement, value: string) => (node.value = value), contents = [], - onMouseMove = () => {}, - onCaretMove = () => {}, + onMouseMove = (value: { name: string; value: string; position: string }) => + value, + onCaretMove = (value: { name: string; value: string; position: string }) => + value, + }: { + read: (node: HTMLInputElement) => string; + write: (node: HTMLInputElement, value: string) => void; + contents: { + name: string; + value: string; + match: (value: string) => boolean; + }[]; + onMouseMove: (value: { + name: string; + value: string; + position: string; + }) => void; + onCaretMove: (value: { + name: string; + value: string; + position: string; + }) => void; } -) => { - const eventNames = ["keyup", "pointermove"]; - const handleEvent = ({ type, offsetX }) => { +): { + update: (value: string) => void; + disconnectedCallback: () => void; +} => { + const eventNames = ["keyup", "pointermove"] as const; + const handleEvent = (event: KeyboardEvent | PointerEvent): void => { + let { offsetX } = event as PointerEvent; + const { type } = event; const targetValue = read(targetNode); const targetPadding = measureTextPadding(targetNode, targetValue); if (type === eventNames[0]) offsetX = targetPadding + - measureTextWidth(targetValue.slice(0, targetNode.selectionStart)); + measureTextWidth( + targetValue.slice(0, targetNode.selectionStart || undefined) + ); const targetMetrics = measureTextMetrics( targetValue, offsetX, @@ -95,18 +124,19 @@ export const createContentController = ( } } }; + eventNames.forEach((eventName) => - targetNode.addEventListener(eventName, handleEvent) + targetNode.addEventListener(eventName, handleEvent, false) ); return { update: (value) => { - let { selectionStart, selectionEnd } = targetNode; - write(value); - targetNode.setSelectionRange?.(selectionStart, selectionEnd); + const { selectionStart, selectionEnd } = targetNode; + write(targetNode, value); + targetNode.setSelectionRange(selectionStart, selectionEnd); }, disconnectedCallback: () => { - eventName.forEach((eventName) => - targetNode.addEventListener(eventName, handleEvent) + eventNames.forEach((eventName) => + targetNode.removeEventListener(eventName, handleEvent, false) ); }, }; diff --git a/app/shared/text-editor/example-text-editor.stories.tsx b/app/shared/text-editor/example-text-editor.stories.tsx index 01dbe938a5..a9b1e12f67 100644 --- a/app/shared/text-editor/example-text-editor.stories.tsx +++ b/app/shared/text-editor/example-text-editor.stories.tsx @@ -11,7 +11,7 @@ const onChange = (editorState: EditorState) => { // Read the contents of the EditorState here. const root = $getRoot(); const selection = $getSelection(); - + // eslint-disable-next-line console.log(root, selection); }); }; From 94332b3f73a2d0df52622952f2fe05420f938ed5 Mon Sep 17 00:00:00 2001 From: Sultan <105234327+natlusyht@users.noreply.github.com> Date: Thu, 30 Jun 2022 23:24:07 +0300 Subject: [PATCH 05/11] fixed types --- .../components/primitives/createContentController.stories.tsx | 3 ++- .../components/primitives/createContentController.ts | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/shared/design-system/components/primitives/createContentController.stories.tsx b/app/shared/design-system/components/primitives/createContentController.stories.tsx index 091859f1a8..79d2b58a09 100644 --- a/app/shared/design-system/components/primitives/createContentController.stories.tsx +++ b/app/shared/design-system/components/primitives/createContentController.stories.tsx @@ -13,6 +13,7 @@ const useContentController = ({ callback: (value: { name: string; value: string }) => void; }) => { useEffect(() => { + if (ref.current == null) return; const { disconnectedCallback } = createContentController(ref.current, { contents: [ { @@ -23,7 +24,7 @@ const useContentController = ({ name: "number", match: (value: string) => isNaN(parseFloat(value)) === false, }, - { name: "unknown", match: (value: string) => value }, + { name: "unknown", match: (value: string) => Boolean(value) }, ], read, write, diff --git a/app/shared/design-system/components/primitives/createContentController.ts b/app/shared/design-system/components/primitives/createContentController.ts index 667c05c01b..47662fed25 100644 --- a/app/shared/design-system/components/primitives/createContentController.ts +++ b/app/shared/design-system/components/primitives/createContentController.ts @@ -72,7 +72,6 @@ export const createContentController = ( write: (node: HTMLInputElement, value: string) => void; contents: { name: string; - value: string; match: (value: string) => boolean; }[]; onMouseMove: (value: { From e30568bf5914503a65e1d29c70e1d3aa492420ec Mon Sep 17 00:00:00 2001 From: Sultan <105234327+natlusyht@users.noreply.github.com> Date: Fri, 1 Jul 2022 01:40:44 +0300 Subject: [PATCH 06/11] lint fix --- ...roller.stories.tsx => create-content-controller.stories.tsx} | 2 +- ...{createContentController.ts => create-content-controller.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename app/shared/design-system/components/primitives/{createContentController.stories.tsx => create-content-controller.stories.tsx} (96%) rename app/shared/design-system/components/primitives/{createContentController.ts => create-content-controller.ts} (100%) diff --git a/app/shared/design-system/components/primitives/createContentController.stories.tsx b/app/shared/design-system/components/primitives/create-content-controller.stories.tsx similarity index 96% rename from app/shared/design-system/components/primitives/createContentController.stories.tsx rename to app/shared/design-system/components/primitives/create-content-controller.stories.tsx index 79d2b58a09..c642771a56 100644 --- a/app/shared/design-system/components/primitives/createContentController.stories.tsx +++ b/app/shared/design-system/components/primitives/create-content-controller.stories.tsx @@ -1,5 +1,5 @@ import { useState, useRef, useEffect, RefObject } from "react"; -import { createContentController } from "./createContentController"; +import { createContentController } from "./create-content-controller"; const useContentController = ({ ref, diff --git a/app/shared/design-system/components/primitives/createContentController.ts b/app/shared/design-system/components/primitives/create-content-controller.ts similarity index 100% rename from app/shared/design-system/components/primitives/createContentController.ts rename to app/shared/design-system/components/primitives/create-content-controller.ts From 5292cd88290ad7555ace313c2bd42a2e976d80ee Mon Sep 17 00:00:00 2001 From: Sultan <105234327+natlusyht@users.noreply.github.com> Date: Tue, 5 Jul 2022 22:31:56 +0300 Subject: [PATCH 07/11] code nit: argTypes. not used. --- .../primitives/create-content-controller.stories.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/shared/design-system/components/primitives/create-content-controller.stories.tsx b/app/shared/design-system/components/primitives/create-content-controller.stories.tsx index c642771a56..4110771803 100644 --- a/app/shared/design-system/components/primitives/create-content-controller.stories.tsx +++ b/app/shared/design-system/components/primitives/create-content-controller.stories.tsx @@ -61,12 +61,8 @@ const ExampleContentController = () => { ); }; -export const DetectInput = Object.assign(ExampleContentController.bind({}), { - args: {}, -}); +export const DetectInput = Object.assign(ExampleContentController.bind({})); export default { - title: "createContentController", component: ExampleContentController, - argTypes: {}, }; From 1a2f3f48ec17c5d0146eec07fd52c8cf2f251688 Mon Sep 17 00:00:00 2001 From: Sara Vieira Date: Tue, 5 Jul 2022 22:06:45 +0200 Subject: [PATCH 08/11] fix foreign constrain --- app/routes/rest/project/clone.$domain.ts | 16 ++++++++++++++-- app/shared/db/user.server.ts | 12 ++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/app/routes/rest/project/clone.$domain.ts b/app/routes/rest/project/clone.$domain.ts index 69d58a52c8..637d5d85d6 100644 --- a/app/routes/rest/project/clone.$domain.ts +++ b/app/routes/rest/project/clone.$domain.ts @@ -4,14 +4,21 @@ import * as db from "~/shared/db"; import { ensureUserCookie } from "~/shared/session"; import config from "~/config"; import type { Project } from "~/shared/db/project.server"; +import { authenticator } from "~/services/auth.server"; +import { createDemoUser } from "~/shared/db/user.server"; const ensureProject = async ({ userId, domain, + demoUserId, }: { userId: User["id"]; domain: string; + demoUserId: boolean; }): Promise => { + if (demoUserId) { + await createDemoUser(userId); + } const projects = await db.project.loadManyByUserId(userId); if (projects.length !== 0) return projects[0]; @@ -28,9 +35,14 @@ const ensureProject = async ({ */ export const loader: LoaderFunction = async ({ request, params }) => { if (params.domain === undefined) return { errors: "Domain required" }; - const { headers, userId } = await ensureUserCookie(request); + const user = await authenticator.isAuthenticated(request); + const { headers, userId: generatedUserId } = await ensureUserCookie(request); try { - const project = await ensureProject({ userId, domain: params.domain }); + const project = await ensureProject({ + userId: user ? user.id : generatedUserId, + domain: params.domain, + demoUserId: user?.id === undefined, + }); return redirect(`${config.designerPath}/${project?.id}`, { headers }); } catch (error: unknown) { if (error instanceof Error) { diff --git a/app/shared/db/user.server.ts b/app/shared/db/user.server.ts index 5524ec8e0a..6c8c639c6f 100644 --- a/app/shared/db/user.server.ts +++ b/app/shared/db/user.server.ts @@ -3,6 +3,18 @@ import { GitHubProfile } from "remix-auth-github"; import { GoogleProfile } from "remix-auth-google"; import { prisma } from "./prisma.server"; +export const createDemoUser = async (userId: string) => { + await prisma.team.create({ + data: { + users: { + create: { + id: userId, + }, + }, + }, + }); +}; + const genericCreateAccount = async ( userData: { email: string; From d02cfeea44b8e2eece11a5135e490413b1a6cbbc Mon Sep 17 00:00:00 2001 From: Sara Vieira Date: Tue, 5 Jul 2022 22:10:28 +0200 Subject: [PATCH 09/11] better variable names --- app/routes/rest/project/clone.$domain.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/routes/rest/project/clone.$domain.ts b/app/routes/rest/project/clone.$domain.ts index 637d5d85d6..d2a7ad2465 100644 --- a/app/routes/rest/project/clone.$domain.ts +++ b/app/routes/rest/project/clone.$domain.ts @@ -10,13 +10,13 @@ import { createDemoUser } from "~/shared/db/user.server"; const ensureProject = async ({ userId, domain, - demoUserId, + isDemoUser, }: { userId: User["id"]; domain: string; - demoUserId: boolean; + isDemoUser: boolean; }): Promise => { - if (demoUserId) { + if (isDemoUser) { await createDemoUser(userId); } const projects = await db.project.loadManyByUserId(userId); @@ -41,7 +41,7 @@ export const loader: LoaderFunction = async ({ request, params }) => { const project = await ensureProject({ userId: user ? user.id : generatedUserId, domain: params.domain, - demoUserId: user?.id === undefined, + isDemoUser: user?.id === undefined, }); return redirect(`${config.designerPath}/${project?.id}`, { headers }); } catch (error: unknown) { From 4ebe006577eb18f20824980417ff5ce7c03ce2e8 Mon Sep 17 00:00:00 2001 From: Sara Vieira Date: Tue, 5 Jul 2022 22:40:48 +0200 Subject: [PATCH 10/11] actually fix it now --- app/canvas/canvas.tsx | 6 +- app/canvas/shared/sync/use-sync.ts | 4 +- app/routes/auth/github.tsx | 6 +- app/routes/auth/github/callback.tsx | 6 +- app/routes/auth/google.tsx | 6 +- app/routes/auth/google/callback.tsx | 5 -- app/routes/rest/project/clone.$domain.ts | 15 ++--- app/services/auth.server.ts | 16 ++--- app/shared/db/index.ts | 1 + app/shared/db/user.server.ts | 83 +++++++++--------------- 10 files changed, 49 insertions(+), 99 deletions(-) diff --git a/app/canvas/canvas.tsx b/app/canvas/canvas.tsx index 48590d9a3a..54698f3a67 100644 --- a/app/canvas/canvas.tsx +++ b/app/canvas/canvas.tsx @@ -2,6 +2,7 @@ import { useCallback, useMemo, useState } from "react"; import { DndProvider } from "react-dnd"; import { TouchBackend } from "react-dnd-touch-backend"; import store from "immerhin"; +import * as db from "~/shared/db"; import { type OnChangeChildren, type Data, @@ -41,7 +42,6 @@ import { useHandleBreakpoints, useInitializeBreakpoints, } from "./shared/breakpoints"; -import type { Project } from "~/shared/db/project.server"; import { rootInstanceContainer, useBreakpoints, @@ -102,7 +102,7 @@ const PreviewMode = () => { type DesignModeProps = { treeId: Tree["id"]; - project: Project; + project: db.project.Project; }; const dndOptions = { enableMouseEvents: true }; @@ -141,7 +141,7 @@ const DesignMode = ({ treeId, project }: DesignModeProps) => { }; type CanvasProps = { - data: Data & { project: Project }; + data: Data & { project: db.project.Project }; }; export const Canvas = ({ data }: CanvasProps): JSX.Element | null => { diff --git a/app/canvas/shared/sync/use-sync.ts b/app/canvas/shared/sync/use-sync.ts index 9250d9e559..d2a1e5080d 100644 --- a/app/canvas/shared/sync/use-sync.ts +++ b/app/canvas/shared/sync/use-sync.ts @@ -1,9 +1,9 @@ import useInterval from "react-use/lib/useInterval"; import { sync } from "immerhin"; import { enqueue } from "./queue"; -import type { Project } from "~/shared/db/project.server"; +import * as db from "~/shared/db"; -export const useSync = ({ project }: { project: Project }) => { +export const useSync = ({ project }: { project: db.project.Project }) => { useInterval(() => { const entries = sync(); if (entries.length === 0) return; diff --git a/app/routes/auth/github.tsx b/app/routes/auth/github.tsx index 4754126d46..fdec6900be 100644 --- a/app/routes/auth/github.tsx +++ b/app/routes/auth/github.tsx @@ -2,7 +2,7 @@ import { ActionFunction, redirect } from "@remix-run/node"; import config from "~/config"; import { authenticator } from "~/services/auth.server"; import { sentryException } from "~/shared/sentry"; -import { ensureUserCookie, AUTH_PROVIDERS } from "~/shared/session"; +import { AUTH_PROVIDERS } from "~/shared/session"; export default function GH() { return null; @@ -10,11 +10,7 @@ export default function GH() { export const action: ActionFunction = async ({ request }) => { try { - const { userId } = await ensureUserCookie(request); return await authenticator.authenticate("github", request, { - context: { - userId, - }, successRedirect: config.dashboardPath, throwOnError: true, }); diff --git a/app/routes/auth/github/callback.tsx b/app/routes/auth/github/callback.tsx index 92660d1b78..0314b96268 100644 --- a/app/routes/auth/github/callback.tsx +++ b/app/routes/auth/github/callback.tsx @@ -1,10 +1,9 @@ import { LoaderFunction, redirect } from "@remix-run/node"; import config from "~/config"; import { authenticator } from "~/services/auth.server"; -import { ensureUserCookie, AUTH_PROVIDERS } from "~/shared/session"; +import { AUTH_PROVIDERS } from "~/shared/session"; export const loader: LoaderFunction = async ({ request }) => { - const { userId } = await ensureUserCookie(request); const url = new URL(request.url); const error = url.searchParams.get("error"); const error_description = url.searchParams.get("error_description"); @@ -16,9 +15,6 @@ export const loader: LoaderFunction = async ({ request }) => { ); } return authenticator.authenticate("github", request, { - context: { - userId, - }, successRedirect: config.dashboardPath, failureRedirect: config.loginPath, }); diff --git a/app/routes/auth/google.tsx b/app/routes/auth/google.tsx index f04c3f8ff8..380bb202c3 100644 --- a/app/routes/auth/google.tsx +++ b/app/routes/auth/google.tsx @@ -2,7 +2,7 @@ import { ActionFunction, LoaderFunction, redirect } from "@remix-run/node"; import config from "~/config"; import { authenticator } from "~/services/auth.server"; import { sentryException } from "~/shared/sentry"; -import { ensureUserCookie, AUTH_PROVIDERS } from "~/shared/session"; +import { AUTH_PROVIDERS } from "~/shared/session"; export default function Google() { return null; @@ -12,11 +12,7 @@ export const loader: LoaderFunction = () => redirect("/login"); export const action: ActionFunction = async ({ request }) => { try { - const { userId } = await ensureUserCookie(request); return await authenticator.authenticate("google", request, { - context: { - userId, - }, successRedirect: config.dashboardPath, throwOnError: true, }); diff --git a/app/routes/auth/google/callback.tsx b/app/routes/auth/google/callback.tsx index f56616122d..5eaf1b129a 100644 --- a/app/routes/auth/google/callback.tsx +++ b/app/routes/auth/google/callback.tsx @@ -1,15 +1,10 @@ import { LoaderFunction } from "@remix-run/node"; import config from "~/config"; import { authenticator } from "~/services/auth.server"; -import { ensureUserCookie } from "~/shared/session"; export const loader: LoaderFunction = async ({ request }) => { - const { userId } = await ensureUserCookie(request); return authenticator.authenticate("google", request, { successRedirect: config.dashboardPath, failureRedirect: config.loginPath, - context: { - userId, - }, }); }; diff --git a/app/routes/rest/project/clone.$domain.ts b/app/routes/rest/project/clone.$domain.ts index d2a7ad2465..6c4a09021c 100644 --- a/app/routes/rest/project/clone.$domain.ts +++ b/app/routes/rest/project/clone.$domain.ts @@ -3,22 +3,15 @@ import type { User } from "@webstudio-is/sdk"; import * as db from "~/shared/db"; import { ensureUserCookie } from "~/shared/session"; import config from "~/config"; -import type { Project } from "~/shared/db/project.server"; import { authenticator } from "~/services/auth.server"; -import { createDemoUser } from "~/shared/db/user.server"; const ensureProject = async ({ userId, domain, - isDemoUser, }: { userId: User["id"]; domain: string; - isDemoUser: boolean; -}): Promise => { - if (isDemoUser) { - await createDemoUser(userId); - } +}): Promise => { const projects = await db.project.loadManyByUserId(userId); if (projects.length !== 0) return projects[0]; @@ -38,10 +31,12 @@ export const loader: LoaderFunction = async ({ request, params }) => { const user = await authenticator.isAuthenticated(request); const { headers, userId: generatedUserId } = await ensureUserCookie(request); try { - const project = await ensureProject({ + const userId = await db.user.ensureUser({ userId: user ? user.id : generatedUserId, + }); + const project = await ensureProject({ + userId, domain: params.domain, - isDemoUser: user?.id === undefined, }); return redirect(`${config.designerPath}/${project?.id}`, { headers }); } catch (error: unknown) { diff --git a/app/services/auth.server.ts b/app/services/auth.server.ts index 82ebc85ebd..06b7456c93 100644 --- a/app/services/auth.server.ts +++ b/app/services/auth.server.ts @@ -4,12 +4,8 @@ import { FormStrategy } from "remix-auth-form"; import { GitHubStrategy } from "remix-auth-github"; import { GoogleStrategy } from "remix-auth-google"; import config from "~/config"; +import * as db from "~/shared/db"; import { sessionStorage } from "~/services/session.server"; -import { - createOrLoginWithDev, - createOrLoginWithGithub, - createOrLoginWithGoogle, -} from "~/shared/db/user.server"; import { sentryException } from "~/shared/sentry"; import { AUTH_PROVIDERS } from "~/shared/session"; @@ -25,8 +21,8 @@ const github = new GitHubStrategy( clientSecret: process.env.GH_CLIENT_SECRET as string, callbackURL: `${url}${config.githubCallbackPath}`, }, - async ({ profile, context }) => { - const user = await createOrLoginWithGithub(profile, context?.userId); + async ({ profile }) => { + const user = await db.user.createOrLoginWithGithub(profile); return user; } @@ -38,8 +34,8 @@ const google = new GoogleStrategy( clientSecret: process.env.GOOGLE_CLIENT_SECRET as string, callbackURL: `${url}${config.googleCallbackPath}`, }, - async ({ profile, context }) => { - const user = await createOrLoginWithGoogle(profile, context?.userId); + async ({ profile }) => { + const user = await db.user.createOrLoginWithGoogle(profile); return user; } @@ -61,7 +57,7 @@ if (process.env.DEV_LOGIN === "true") { if (secret === process.env.AUTH_SECRET) { try { - const user = await createOrLoginWithDev(secret); + const user = await db.user.createOrLoginWithDev(); return user; } catch (error: unknown) { if (error instanceof Error) { diff --git a/app/shared/db/index.ts b/app/shared/db/index.ts index b220b5bc0b..8e5524eaa7 100644 --- a/app/shared/db/index.ts +++ b/app/shared/db/index.ts @@ -2,5 +2,6 @@ export * as tree from "./tree.server"; export * as project from "./project.server"; export * as props from "./props.server"; export * as misc from "./misc.server"; +export * as user from "./user.server"; export * from "./canvas.server"; export * as breakpoints from "./breakpoints.server"; diff --git a/app/shared/db/user.server.ts b/app/shared/db/user.server.ts index 6c8c639c6f..5eb983c1b9 100644 --- a/app/shared/db/user.server.ts +++ b/app/shared/db/user.server.ts @@ -15,50 +15,34 @@ export const createDemoUser = async (userId: string) => { }); }; -const genericCreateAccount = async ( - userData: { - email: string; - username: string; - image: string; - provider: string; - }, - userId: string -): Promise => { - const existingUserWithId = await prisma.user.findUnique({ - where: { - id: userId, - }, - }); - if (existingUserWithId) { - const connectedUser = await prisma.user.update({ - where: { - id: userId, - }, - data: userData, - }); - - if (connectedUser.teamId) { - return connectedUser; - } - - await prisma.team.create({ - data: { - users: { - connect: { - id: connectedUser.id, - }, - }, - }, - }); - - return connectedUser; +export const ensureUser = async ({ userId }: { userId: string }) => { + // Always check if the account userId exists because account could have been deleted + // or we could be in a demo mode with a generated user + const isUserCreated = Boolean( + await prisma.user.findUnique({ + where: { id: userId }, + }) + ); + if (isUserCreated) { + return userId; } + await createDemoUser(userId); + return userId; +}; + +const genericCreateAccount = async (userData: { + email: string; + username: string; + image: string; + provider: string; +}): Promise => { const existingUserWithEmail = await prisma.user.findUnique({ where: { email: userData.email, }, }); + console.log(existingUserWithEmail); if (existingUserWithEmail) { if (existingUserWithEmail.teamId) { @@ -80,18 +64,11 @@ const genericCreateAccount = async ( const newTeam = await prisma.team.create({ data: { users: { - create: { - id: userId, - ...userData, - }, + create: userData, }, }, include: { - users: { - where: { - id: userId, - }, - }, + users: true, }, }); @@ -99,8 +76,7 @@ const genericCreateAccount = async ( }; export const createOrLoginWithGithub = async ( - profile: GitHubProfile, - userId: string + profile: GitHubProfile ): Promise => { const userData = { email: profile._json.email, @@ -108,13 +84,12 @@ export const createOrLoginWithGithub = async ( image: profile._json.avatar_url, provider: profile.provider, }; - const newUser = await genericCreateAccount(userData, userId); + const newUser = await genericCreateAccount(userData); return newUser; }; export const createOrLoginWithGoogle = async ( - profile: GoogleProfile, - userId: string + profile: GoogleProfile ): Promise => { const userData = { email: profile._json.email, @@ -122,11 +97,11 @@ export const createOrLoginWithGoogle = async ( image: profile._json.picture, provider: profile.provider, }; - const newUser = await genericCreateAccount(userData, userId); + const newUser = await genericCreateAccount(userData); return newUser; }; -export const createOrLoginWithDev = async (userId: string): Promise => { +export const createOrLoginWithDev = async (): Promise => { const userData = { email: "hello@webstudio.is", username: "admin", @@ -134,6 +109,6 @@ export const createOrLoginWithDev = async (userId: string): Promise => { provider: "dev", }; - const newUser = await genericCreateAccount(userData, userId); + const newUser = await genericCreateAccount(userData); return newUser; }; From d9bdf4c31a37e7c9a4c6c8da6030b8f8711fe4bc Mon Sep 17 00:00:00 2001 From: Sara Vieira Date: Tue, 5 Jul 2022 22:41:32 +0200 Subject: [PATCH 11/11] remove console.log --- app/shared/db/user.server.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/shared/db/user.server.ts b/app/shared/db/user.server.ts index 5eb983c1b9..24735e8be5 100644 --- a/app/shared/db/user.server.ts +++ b/app/shared/db/user.server.ts @@ -42,7 +42,6 @@ const genericCreateAccount = async (userData: { email: userData.email, }, }); - console.log(existingUserWithEmail); if (existingUserWithEmail) { if (existingUserWithEmail.teamId) {