mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-10-29 08:07:48 +09:00
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
This commit is contained in:
1 parent
54a9303a47
commit
00c02201a0
11 files changed
+80
-23
No files matched your search
@@ -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";
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<ScrollArea>
|
||||
@@ -308,7 +324,7 @@ const Content = (props: PublishButtonProps) => {
|
||||
</>
|
||||
)}
|
||||
|
||||
<Publish projectId={props.project.id} />
|
||||
<Publish projectId={props.project.id} domains={domainsToPublish} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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<AppContext["authorization"]> => {
|
||||
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<AppContext> => {
|
||||
const authorization = await createAuthorizationContext(request, buildEnv);
|
||||
export const createContext = async (request: Request): Promise<AppContext> => {
|
||||
const authorization = await createAuthorizationContext(request);
|
||||
const domain = createDomainContext(request);
|
||||
const deployment = createDeploymentContext(request);
|
||||
|
||||
|
||||
@@ -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],
|
||||
};
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./trpc";
|
||||
export * from "./db";
|
||||
@@ -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
|
||||
|
||||
@@ -188,6 +188,8 @@ export const cloneBuild = async (
|
||||
const data = {
|
||||
...build,
|
||||
id: undefined,
|
||||
createdAt: undefined,
|
||||
updatedAt: undefined,
|
||||
deployment: props.deployment
|
||||
? serializeDeployment(props.deployment)
|
||||
: undefined,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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"];
|
||||
|
||||
Reference in new issue
Block a user