From 00c02201a052d915d0b292671d7eda84fdfe7bc3 Mon Sep 17 00:00:00 2001 From: Ivan Starkov Date: Thu, 1 Jun 2023 17:59:10 +0400 Subject: [PATCH] Add domains to publish (#1694) ## Description At publish we would allow to check domains we want to publish. As of now we publish all project domains. But already pass them from UI, so future move would be just UI changes, without touching API - [x] - Fix "no access to project domains using service api call" ## Steps for reproduction 1. click button 2. expect xyz ## Code Review - [ ] hi @kof, I need you to do - conceptual review (architecture, feature-correctness) - detailed review (read every line) - test it on preview ## Before requesting a review - [ ] made a self-review - [ ] added inline comments where things may be not obvious (the "why", not "what") ## Before merging - [ ] tested locally and on preview environment (preview dev login: 5de6) - [ ] updated [test cases](https://github.com/webstudio-is/webstudio-builder/blob/main/apps/builder/docs/test-cases.md) document - [ ] added tests - [ ] if any new env variables are added, added them to `.env.example` and the `builder/env-check.js` if mandatory --- .../app/builder/features/topbar/domains.tsx | 9 +++-- .../app/builder/features/topbar/publish.tsx | 24 ++++++++++-- .../app/routes/rest.build.$buildId.tsx | 2 +- apps/builder/app/shared/context.server.ts | 16 ++++---- apps/builder/app/shared/db/canvas.server.ts | 37 ++++++++++++++++++- packages/domain/src/db/domain.ts | 2 +- packages/domain/src/index.server.ts | 1 + packages/domain/src/trpc/domain.ts | 4 +- packages/project-build/src/db/build.ts | 2 + .../src/authorize/project.server.ts | 2 +- .../src/context/context.server.ts | 4 +- 11 files changed, 80 insertions(+), 23 deletions(-) diff --git a/apps/builder/app/builder/features/topbar/domains.tsx b/apps/builder/app/builder/features/topbar/domains.tsx index 025dff107e..a770d746aa 100644 --- a/apps/builder/app/builder/features/topbar/domains.tsx +++ b/apps/builder/app/builder/features/topbar/domains.tsx @@ -67,11 +67,14 @@ const getCname = (domain: string) => { return cnameArray.join("."); }; -const getStatusText = (props: { projectDomain: Domain }) => { - const status = props.projectDomain.verified - ? (`VERIFIED_${props.projectDomain.domain.status}` as `VERIFIED_${DomainStatus}`) +export const getStatus = (projectDomain: Domain) => + projectDomain.verified + ? (`VERIFIED_${projectDomain.domain.status}` as const) : `UNVERIFIED`; +const getStatusText = (props: { projectDomain: Domain }) => { + const status = getStatus(props.projectDomain); + let isVerifiedActive = false; let text = "Something went wrong"; diff --git a/apps/builder/app/builder/features/topbar/publish.tsx b/apps/builder/app/builder/features/topbar/publish.tsx index 5dffe0f673..15a88f6712 100644 --- a/apps/builder/app/builder/features/topbar/publish.tsx +++ b/apps/builder/app/builder/features/topbar/publish.tsx @@ -23,7 +23,7 @@ import { getPublishedUrl } from "~/shared/router-utils"; import { theme } from "@webstudio-is/design-system"; import { useAuthPermit } from "~/shared/nano-states"; import { isFeatureEnabled } from "@webstudio-is/feature-flags"; -import { Domains } from "./domains"; +import { Domains, getStatus } from "./domains"; import { CollapsibleDomainSection } from "./collapsible-domain-section"; import { CheckCircleIcon, @@ -207,7 +207,13 @@ const ChangeProjectDomain = (props: PublishButtonProps) => { ); }; -const Publish = ({ projectId }: { projectId: Project["id"] }) => { +const Publish = ({ + projectId, + domains, +}: { + projectId: Project["id"]; + domains: string[]; +}) => { const { send: publish, state: publishState, @@ -239,7 +245,7 @@ const Publish = ({ projectId }: { projectId: Project["id"] }) => { color="positive" disabled={publishState !== "idle"} onClick={() => { - publish({ projectId }); + publish({ projectId, domains }); }} > Publish @@ -260,6 +266,16 @@ const Content = (props: PublishButtonProps) => { refreshDomainResult({ projectId: props.project.id }); }, [refreshDomainResult, props.project.id]); + // In the future we would allow to select what domains to publish, + // now we just publish all verified and active domains + const domainsToPublish = domainsResult?.success + ? domainsResult.data + .filter( + (projectDomain) => getStatus(projectDomain) === "VERIFIED_ACTIVE" + ) + .map((projectDomain) => projectDomain.domain.domain) + : []; + return ( <> @@ -308,7 +324,7 @@ const Content = (props: PublishButtonProps) => { )} - + ); }; diff --git a/apps/builder/app/routes/rest.build.$buildId.tsx b/apps/builder/app/routes/rest.build.$buildId.tsx index 9820714ee8..9d010d4350 100644 --- a/apps/builder/app/routes/rest.build.$buildId.tsx +++ b/apps/builder/app/routes/rest.build.$buildId.tsx @@ -21,7 +21,7 @@ export const loader = async ({ throw json("Required project id", { status: 400 }); } - const context = await createContext(request, "prod"); + const context = await createContext(request); const pagesCanvasData = await loadProductionCanvasData(buildId, context); diff --git a/apps/builder/app/shared/context.server.ts b/apps/builder/app/shared/context.server.ts index ccf25b2f61..813e9721a5 100644 --- a/apps/builder/app/shared/context.server.ts +++ b/apps/builder/app/shared/context.server.ts @@ -4,8 +4,7 @@ import { authenticator } from "~/services/auth.server"; import { trpcClient } from "~/services/trpc.server"; const createAuthorizationContext = async ( - request: Request, - buildEnv: AppContext["authorization"]["buildEnv"] + request: Request ): Promise => { const url = new URL(request.url); @@ -13,10 +12,14 @@ const createAuthorizationContext = async ( const user = await authenticator.isAuthenticated(request); + const isServiceCall = + request.headers.has("Authorization") && + request.headers.get("Authorization") === env.TRPC_SERVER_API_TOKEN; + const context: AppContext["authorization"] = { userId: user?.id, authToken, - buildEnv, + isServiceCall, authorizeTrpc: trpcClient.authorize, }; @@ -48,11 +51,8 @@ const createDeploymentContext = (request: Request) => { /** * argument buildEnv==="prod" only if we are loading project with production build */ -export const createContext = async ( - request: Request, - buildEnv: AppContext["authorization"]["buildEnv"] = "dev" -): Promise => { - const authorization = await createAuthorizationContext(request, buildEnv); +export const createContext = async (request: Request): Promise => { + const authorization = await createAuthorizationContext(request); const domain = createDomainContext(request); const deployment = createDeploymentContext(request); diff --git a/apps/builder/app/shared/db/canvas.server.ts b/apps/builder/app/shared/db/canvas.server.ts index e1ee794a44..0af85715f4 100644 --- a/apps/builder/app/shared/db/canvas.server.ts +++ b/apps/builder/app/shared/db/canvas.server.ts @@ -4,6 +4,7 @@ import { loadAssetsByProject } from "@webstudio-is/asset-uploader/index.server"; import type { AppContext } from "@webstudio-is/trpc-interface/index.server"; import { findPageByIdOrPath } from "@webstudio-is/project-build"; import type { Build } from "@webstudio-is/prisma-client"; +import { db as domainDb } from "@webstudio-is/domain/index.server"; export const loadProductionCanvasData = async ( buildId: Build["id"], @@ -15,6 +16,34 @@ export const loadProductionCanvasData = async ( throw new Error("The project is not published"); } + const { deployment } = build; + + if (deployment === undefined) { + throw new Error("The project is not published"); + } + + const currentProjectDomainsResult = await domainDb.findMany( + { projectId: build.projectId }, + context + ); + + if (currentProjectDomainsResult.success === false) { + throw new Error(currentProjectDomainsResult.error); + } + + const currentProjectDomains = currentProjectDomainsResult.data; + + // Check that build deployment domains are still active and verified + // for examle: redeploy created few days later + const domains = deployment.domains.filter((domain) => + currentProjectDomains.some( + (projectDomain) => + projectDomain.domain.domain === domain && + projectDomain.domain.status === "ACTIVE" && + projectDomain.verified + ) + ); + const page = findPageByIdOrPath(build.pages, "/"); if (page === undefined) { @@ -24,7 +53,13 @@ export const loadProductionCanvasData = async ( const allAssets = await loadAssetsByProject(build.projectId, context); const canvasData = { - build, + build: { + ...build, + deployment: { + ...deployment, + domains, + }, + }, page, pages: [build.pages.homePage, ...build.pages.pages], }; diff --git a/packages/domain/src/db/domain.ts b/packages/domain/src/db/domain.ts index 3597974131..b5c52a4841 100644 --- a/packages/domain/src/db/domain.ts +++ b/packages/domain/src/db/domain.ts @@ -32,7 +32,7 @@ export const findMany = async ( > => { // Only builder of the project can list domains const canList = await authorizeProject.hasProjectPermit( - { projectId: props.projectId, permit: "build" }, + { projectId: props.projectId, permit: "view" }, context ); diff --git a/packages/domain/src/index.server.ts b/packages/domain/src/index.server.ts index 7caf469ef2..de6f4b1d04 100644 --- a/packages/domain/src/index.server.ts +++ b/packages/domain/src/index.server.ts @@ -1 +1,2 @@ export * from "./trpc"; +export * from "./db"; diff --git a/packages/domain/src/trpc/domain.ts b/packages/domain/src/trpc/domain.ts index 60a24c6328..e4b2b890a9 100644 --- a/packages/domain/src/trpc/domain.ts +++ b/packages/domain/src/trpc/domain.ts @@ -25,14 +25,14 @@ export const domainRouter = router({ } }), publish: procedure - .input(z.object({ projectId: z.string() })) + .input(z.object({ projectId: z.string(), domains: z.array(z.string()) })) .mutation(async ({ input, ctx }) => { try { const build = await createProductionBuild( { projectId: input.projectId, deployment: { - domains: [], + domains: input.domains, }, }, ctx diff --git a/packages/project-build/src/db/build.ts b/packages/project-build/src/db/build.ts index d985cd85b7..8a25817859 100644 --- a/packages/project-build/src/db/build.ts +++ b/packages/project-build/src/db/build.ts @@ -188,6 +188,8 @@ export const cloneBuild = async ( const data = { ...build, id: undefined, + createdAt: undefined, + updatedAt: undefined, deployment: props.deployment ? serializeDeployment(props.deployment) : undefined, diff --git a/packages/trpc-interface/src/authorize/project.server.ts b/packages/trpc-interface/src/authorize/project.server.ts index 55711594e6..4dde10c037 100644 --- a/packages/trpc-interface/src/authorize/project.server.ts +++ b/packages/trpc-interface/src/authorize/project.server.ts @@ -47,7 +47,7 @@ export const hasProjectPermit = async ( const namespace = "Project"; // Allow load production build env i.e. "published" site - if (props.permit === "view" && context.authorization.buildEnv === "prod") { + if (props.permit === "view" && context.authorization.isServiceCall) { return true; } diff --git a/packages/trpc-interface/src/context/context.server.ts b/packages/trpc-interface/src/context/context.server.ts index 101f2c809c..aa9f327397 100644 --- a/packages/trpc-interface/src/context/context.server.ts +++ b/packages/trpc-interface/src/context/context.server.ts @@ -15,9 +15,9 @@ type AuthorizationContext = { authToken: string | undefined; /** - * buildEnv==="prod" only if we are loading project with production build + * Allow service 2 service communications to skip authorization for view calls */ - buildEnv: "dev" | "prod"; + isServiceCall: boolean; // Pass trpcClient through context as only main app can initialize it authorizeTrpc: TrpcInterfaceClient["authorize"];