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 69d58a52c8..6c4a09021c 100644 --- a/app/routes/rest/project/clone.$domain.ts +++ b/app/routes/rest/project/clone.$domain.ts @@ -3,7 +3,7 @@ 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"; const ensureProject = async ({ userId, @@ -11,7 +11,7 @@ const ensureProject = async ({ }: { userId: User["id"]; domain: string; -}): Promise => { +}): Promise => { const projects = await db.project.loadManyByUserId(userId); if (projects.length !== 0) return projects[0]; @@ -28,9 +28,16 @@ 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 userId = await db.user.ensureUser({ + userId: user ? user.id : generatedUserId, + }); + const project = await ensureProject({ + userId, + domain: params.domain, + }); return redirect(`${config.designerPath}/${project?.id}`, { headers }); } catch (error: unknown) { if (error instanceof Error) { 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 5524ec8e0a..24735e8be5 100644 --- a/app/shared/db/user.server.ts +++ b/app/shared/db/user.server.ts @@ -3,45 +3,40 @@ import { GitHubProfile } from "remix-auth-github"; import { GoogleProfile } from "remix-auth-google"; import { prisma } from "./prisma.server"; -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, - }, +export const createDemoUser = async (userId: string) => { + await prisma.team.create({ + data: { + users: { + create: { + id: userId, }, }, - }); + }, + }); +}; - 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, @@ -68,18 +63,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, }, }); @@ -87,8 +75,7 @@ const genericCreateAccount = async ( }; export const createOrLoginWithGithub = async ( - profile: GitHubProfile, - userId: string + profile: GitHubProfile ): Promise => { const userData = { email: profile._json.email, @@ -96,13 +83,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, @@ -110,11 +96,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", @@ -122,6 +108,6 @@ export const createOrLoginWithDev = async (userId: string): Promise => { provider: "dev", }; - const newUser = await genericCreateAccount(userData, userId); + const newUser = await genericCreateAccount(userData); return newUser; };