diff --git a/.commitlintrc.json b/.commitlintrc.json index 60b0b0b68..8ad736340 100644 --- a/.commitlintrc.json +++ b/.commitlintrc.json @@ -21,7 +21,8 @@ "repo", "styling", "observability", - "analytics" + "analytics", + "template" ] ] } diff --git a/apps/api/src/routes/v1/templates/byId.ts b/apps/api/src/routes/v1/templates/byId.ts index 79afe58a5..6fdb2c8c4 100644 --- a/apps/api/src/routes/v1/templates/byId.ts +++ b/apps/api/src/routes/v1/templates/byId.ts @@ -20,18 +20,20 @@ const route = createRoute({ content: { "application/json": { schema: z.object({ - id: z.string(), - name: z.string(), - path: z.string(), - logoUrl: z.string().nullable(), - summary: z.string(), - readme: z.string().nullable(), - deploy: z.string(), - persistentStorageEnabled: z.boolean(), - guide: z.string().nullable(), - githubUrl: z.string(), - config: z.object({ - ssh: z.boolean().optional() + data: z.object({ + id: z.string(), + name: z.string(), + path: z.string(), + logoUrl: z.string().nullable(), + summary: z.string(), + readme: z.string().nullable(), + deploy: z.string(), + persistentStorageEnabled: z.boolean(), + guide: z.string().nullable(), + githubUrl: z.string(), + config: z.object({ + ssh: z.boolean().optional() + }) }) }) } @@ -51,5 +53,5 @@ export default new OpenAPIHono().openapi(route, async c => { return c.text("Template not found", 404); } - return c.json(template); + return c.json({ data: template }); }); diff --git a/apps/deploy-web/src/components/deployments/DeploymentDetail.tsx b/apps/deploy-web/src/components/deployments/DeploymentDetail.tsx index cd7ca3ec9..4ffe8e226 100644 --- a/apps/deploy-web/src/components/deployments/DeploymentDetail.tsx +++ b/apps/deploy-web/src/components/deployments/DeploymentDetail.tsx @@ -1,6 +1,7 @@ "use client"; -import { createRef, useEffect, useState } from "react"; +import { createRef, FC, useEffect, useState } from "react"; +import type { TemplateOutput } from "@akashnetwork/http-sdk/src/template/template-http.service"; import { Alert, Button, buttonVariants, Spinner, Tabs, TabsList, TabsTrigger } from "@akashnetwork/ui/components"; import { cn } from "@akashnetwork/ui/utils"; import { ArrowLeft } from "iconoir-react"; @@ -9,10 +10,8 @@ import { useRouter, useSearchParams } from "next/navigation"; import { NextSeo } from "next-seo"; import { event } from "nextjs-google-analytics"; -import { CI_CD_TEMPLATE_ID } from "@src/config/remote-deploy.config"; import { useCertificate } from "@src/context/CertificateProvider"; import { useSettings } from "@src/context/SettingsProvider"; -import { useTemplates } from "@src/context/TemplatesProvider"; import { useWallet } from "@src/context/WalletProvider"; import { useDeploymentDetail } from "@src/queries/useDeploymentQuery"; import { useDeploymentLeaseList } from "@src/queries/useLeaseQuery"; @@ -31,7 +30,12 @@ import { DeploymentSubHeader } from "./DeploymentSubHeader"; import { LeaseRow } from "./LeaseRow"; import { ManifestUpdate } from "./ManifestUpdate"; -export function DeploymentDetail({ dseq }: React.PropsWithChildren<{ dseq: string }>) { +export interface DeploymentDetailProps { + dseq: string; + remoteDeployTemplate: TemplateOutput; +} + +export const DeploymentDetail: FC = ({ dseq, remoteDeployTemplate }) => { const router = useRouter(); const [activeTab, setActiveTab] = useState("LEASES"); const [editedManifest, setEditedManifest] = useState(null); @@ -39,8 +43,6 @@ export function DeploymentDetail({ dseq }: React.PropsWithChildren<{ dseq: strin const { isSettingsInit } = useSettings(); const [leaseRefs, setLeaseRefs] = useState>([]); const [deploymentManifest, setDeploymentManifest] = useState(null); - const { getTemplateById } = useTemplates(); - const remoteDeployTemplate = getTemplateById(CI_CD_TEMPLATE_ID); const isRemoteDeploy: boolean = !!editedManifest && !!isImageInYaml(editedManifest, remoteDeployTemplate?.deploy); const repo: string | null = isRemoteDeploy ? extractRepositoryUrl(editedManifest) : null; @@ -255,4 +257,4 @@ export function DeploymentDetail({ dseq }: React.PropsWithChildren<{ dseq: strin )} ); -} +}; diff --git a/apps/deploy-web/src/components/templates/TemplateDetail.tsx b/apps/deploy-web/src/components/templates/TemplateDetail.tsx index d38ab6e9d..44736bc9d 100644 --- a/apps/deploy-web/src/components/templates/TemplateDetail.tsx +++ b/apps/deploy-web/src/components/templates/TemplateDetail.tsx @@ -1,5 +1,6 @@ "use client"; -import { useState } from "react"; + +import { FC, useCallback, useState } from "react"; import { Button, buttonVariants, Tabs, TabsList, TabsTrigger } from "@akashnetwork/ui/components"; import { cn } from "@akashnetwork/ui/utils"; import GitHubIcon from "@mui/icons-material/GitHub"; @@ -8,10 +9,8 @@ import Link from "next/link"; import { useRouter } from "next/navigation"; import { DynamicMonacoEditor } from "@src/components/shared/DynamicMonacoEditor"; -import { LinearLoadingSkeleton } from "@src/components/shared/LinearLoadingSkeleton"; import Markdown from "@src/components/shared/Markdown"; import ViewPanel from "@src/components/shared/ViewPanel"; -import { useTemplates } from "@src/context/TemplatesProvider"; import { usePreviousRoute } from "@src/hooks/usePreviousRoute"; import { getShortText } from "@src/hooks/useShortText"; import { ApiTemplate } from "@src/types"; @@ -20,37 +19,37 @@ import { domainName, UrlService } from "@src/utils/urlUtils"; import Layout from "../layout/Layout"; import { CustomNextSeo } from "../shared/CustomNextSeo"; -type Props = { +export interface TemplateDetailProps { templateId: string; template: ApiTemplate; -}; +} -export const TemplateDetail: React.FunctionComponent = ({ templateId, template }) => { +export const TemplateDetail: FC = ({ templateId, template }) => { const [activeTab, setActiveTab] = useState("README"); - const { getTemplateById, isLoading } = useTemplates(); const router = useRouter(); - const _template = template || getTemplateById(templateId); const previousRoute = usePreviousRoute(); - function handleBackClick() { + const goBack = useCallback(() => { if (previousRoute) { router.back(); } else { router.push(UrlService.templates()); } - } + }, [previousRoute, router]); - function handleOpenGithub() { - window.open(_template.githubUrl, "_blank"); - } + const openGithub = useCallback(() => { + if (template?.githubUrl) { + window.open(template.githubUrl, "_blank"); + } + }, [template]); return (
@@ -61,32 +60,31 @@ export const TemplateDetail: React.FunctionComponent = ({ templateId, tem View SDL - {_template?.guide && ( + {template?.guide && ( Guide )} -
-
-

{_template?.name}

+

{template?.name}

-
Deploy  @@ -97,21 +95,21 @@ export const TemplateDetail: React.FunctionComponent = ({ templateId, tem {activeTab === "README" && (
- {_template?.readme} + {template?.readme}
)} {activeTab === "SDL" && (
- +
)} {activeTab === "GUIDE" && (
- {_template?.guide} + {template?.guide}
)} diff --git a/apps/deploy-web/src/context/WalletProvider/WalletProvider.tsx b/apps/deploy-web/src/context/WalletProvider/WalletProvider.tsx index 7c0d43a2a..0ee880187 100644 --- a/apps/deploy-web/src/context/WalletProvider/WalletProvider.tsx +++ b/apps/deploy-web/src/context/WalletProvider/WalletProvider.tsx @@ -19,7 +19,7 @@ import { useManagedWallet } from "@src/hooks/useManagedWallet"; import { useUser } from "@src/hooks/useUser"; import { useWhen } from "@src/hooks/useWhen"; import { useBalances } from "@src/queries/useBalancesQuery"; -import { txHttpService } from "@src/services/http/http.service"; +import { txHttpService } from "@src/services/http/http-browser.service"; import networkStore from "@src/store/networkStore"; import walletStore from "@src/store/walletStore"; import { AnalyticsCategory, AnalyticsEvents } from "@src/types/analytics"; diff --git a/apps/deploy-web/src/pages/deployments/[dseq]/index.tsx b/apps/deploy-web/src/pages/deployments/[dseq]/index.tsx index 29f44ab3a..e6e2d2e31 100644 --- a/apps/deploy-web/src/pages/deployments/[dseq]/index.tsx +++ b/apps/deploy-web/src/pages/deployments/[dseq]/index.tsx @@ -1,19 +1,27 @@ -import { DeploymentDetail } from "@src/components/deployments/DeploymentDetail"; +import type { GetServerSideProps } from "next"; +import { z } from "zod"; -type Props = { - dseq: string; -}; +import { DeploymentDetail, DeploymentDetailProps } from "@src/components/deployments/DeploymentDetail"; +import { CI_CD_TEMPLATE_ID } from "@src/config/remote-deploy.config"; +import { services } from "@src/services/http/http-server.service"; -const DeploymentDetailPage: React.FunctionComponent = ({ dseq }) => { - return ; -}; +export default DeploymentDetail; -export default DeploymentDetailPage; +const contextSchema = z.object({ + params: z.object({ + dseq: z.string() + }) +}); +type Params = z.infer["params"]; + +export const getServerSideProps: GetServerSideProps = async context => { + const { params } = contextSchema.parse(context); + const remoteDeployTemplate = await services.template.findById(CI_CD_TEMPLATE_ID); -export async function getServerSideProps({ params }) { return { props: { - dseq: params?.dseq + remoteDeployTemplate, + dseq: params.dseq } }; -} +}; diff --git a/apps/deploy-web/src/pages/profile/[username]/index.tsx b/apps/deploy-web/src/pages/profile/[username]/index.tsx index 96fc5ae5b..7db42cd05 100644 --- a/apps/deploy-web/src/pages/profile/[username]/index.tsx +++ b/apps/deploy-web/src/pages/profile/[username]/index.tsx @@ -1,7 +1,7 @@ import axios from "axios"; import { UserProfile } from "@src/components/user/UserProfile"; -import { browserEnvConfig } from "@src/config/browser-env.config"; +import { serverEnvConfig } from "@src/config/server-env.config"; import { IUserSetting } from "@src/types/user"; type Props = { @@ -37,6 +37,6 @@ export async function getServerSideProps({ params }) { } async function fetchUser(username: string) { - const response = await axios.get(`${browserEnvConfig.NEXT_PUBLIC_BASE_API_MAINNET_URL}/user/byUsername/${username}`); + const response = await axios.get(`${serverEnvConfig.BASE_API_MAINNET_URL}/user/byUsername/${username}`); return response.data; } diff --git a/apps/deploy-web/src/pages/templates/[templateId]/index.tsx b/apps/deploy-web/src/pages/templates/[templateId]/index.tsx index 4868a8b65..f8a3cd9ea 100644 --- a/apps/deploy-web/src/pages/templates/[templateId]/index.tsx +++ b/apps/deploy-web/src/pages/templates/[templateId]/index.tsx @@ -1,28 +1,21 @@ -import axios from "axios"; +import type { GetServerSideProps } from "next"; +import { z } from "zod"; -import { TemplateDetail } from "@src/components/templates/TemplateDetail"; -import { serverEnvConfig } from "@src/config/server-env.config"; -import { ApiTemplate } from "@src/types"; +import { TemplateDetail, TemplateDetailProps } from "@src/components/templates/TemplateDetail"; +import { services } from "@src/services/http/http-server.service"; -type Props = { - templateId: string; - template: ApiTemplate; -}; - -const TemplateDetailPage: React.FunctionComponent = ({ templateId, template }) => { - return ; -}; +export default TemplateDetail; -export default TemplateDetailPage; +const contextSchema = z.object({ + params: z.object({ + templateId: z.string() + }) +}); +type Params = z.infer["params"]; -export async function getServerSideProps({ params }) { - const response = await axios.get(`${serverEnvConfig.BASE_API_MAINNET_URL}/templates`); - const categories = response.data.filter(x => (x.templates || []).length > 0); - categories.forEach(c => { - c.templates.forEach(t => (t.category = c.title)); - }); - const templates = categories.flatMap(x => x.templates); - const template = templates.find(x => x.id === params?.templateId); +export const getServerSideProps: GetServerSideProps = async context => { + const { params } = contextSchema.parse(context); + const template = await services.template.findById(params.templateId); if (!template) { return { @@ -36,4 +29,4 @@ export async function getServerSideProps({ params }) { template } }; -} +}; diff --git a/apps/deploy-web/src/queries/useAnonymousUserQuery.ts b/apps/deploy-web/src/queries/useAnonymousUserQuery.ts index 002d13596..5f05edaaf 100644 --- a/apps/deploy-web/src/queries/useAnonymousUserQuery.ts +++ b/apps/deploy-web/src/queries/useAnonymousUserQuery.ts @@ -2,7 +2,7 @@ import { useState } from "react"; import * as Sentry from "@sentry/nextjs"; import { useWhen } from "@src/hooks/useWhen"; -import { userHttpService } from "@src/services/http/http.service"; +import { userHttpService } from "@src/services/http/http-browser.service"; export interface UserOutput { id: string; diff --git a/apps/deploy-web/src/queries/useStripePricesQuery.ts b/apps/deploy-web/src/queries/useStripePricesQuery.ts index e2a045816..81bef3703 100644 --- a/apps/deploy-web/src/queries/useStripePricesQuery.ts +++ b/apps/deploy-web/src/queries/useStripePricesQuery.ts @@ -1,6 +1,6 @@ import { useQuery } from "react-query"; -import { stripeService } from "@src/services/http/http.service"; +import { stripeService } from "@src/services/http/http-browser.service"; export function useStripePricesQuery({ enabled = true } = {}) { return useQuery(["StripePrices"], () => stripeService.findPrices(), { diff --git a/apps/deploy-web/src/queries/useTemplateQuery.tsx b/apps/deploy-web/src/queries/useTemplateQuery.tsx index e8a24cd68..3cebe8926 100644 --- a/apps/deploy-web/src/queries/useTemplateQuery.tsx +++ b/apps/deploy-web/src/queries/useTemplateQuery.tsx @@ -130,3 +130,7 @@ export function useTemplates(options = {}) { refetchOnReconnect: false }); } + +export function useTemplateById(id: string, options: UseQueryOptions = {}) { + return useQuery(QueryKeys.getTemplatesKey(), () => axios.get(`${ApiUrlService.templates()}/${id}`).then(response => response.data), options); +} diff --git a/apps/deploy-web/src/services/http-factory/http-factory.service.ts b/apps/deploy-web/src/services/http-factory/http-factory.service.ts new file mode 100644 index 000000000..547661c6d --- /dev/null +++ b/apps/deploy-web/src/services/http-factory/http-factory.service.ts @@ -0,0 +1,31 @@ +import { TxHttpService, UserHttpService } from "@akashnetwork/http-sdk"; +import { StripeService } from "@akashnetwork/http-sdk/src/stripe/stripe.service"; +import { TemplateHttpService } from "@akashnetwork/http-sdk/src/template/template-http.service"; +import { event } from "nextjs-google-analytics"; + +import type { BrowserEnvConfig, ServerEnvConfig } from "@src/config/env-config.schema"; +import { authService } from "@src/services/auth/auth.service"; +import { AnalyticsCategory, AnalyticsEvents } from "@src/types/analytics"; +import { customRegistry } from "@src/utils/customRegistry"; + +export const createServices = (config: Pick | Pick) => { + const apiConfig = { baseURL: "BASE_API_MAINNET_URL" in config ? config.BASE_API_MAINNET_URL : config.NEXT_PUBLIC_BASE_API_MAINNET_URL }; + + const user = new UserHttpService(apiConfig); + const stripe = new StripeService(apiConfig); + const tx = new TxHttpService(customRegistry, apiConfig); + const template = new TemplateHttpService(apiConfig); + + user.interceptors.request.use(authService.withAnonymousUserHeader); + stripe.interceptors.request.use(authService.withAnonymousUserHeader); + tx.interceptors.request.use(authService.withAnonymousUserHeader); + + user.interceptors.response.use(response => { + if (response.config.url?.startsWith("/v1/anonymous-users") && response.config.method === "post" && response.status === 200) { + event(AnalyticsEvents.ANONYMOUS_USER_CREATED, { category: AnalyticsCategory.USER, label: "Anonymous User Created" }); + } + return response; + }); + + return { user, stripe, tx, template }; +}; diff --git a/apps/deploy-web/src/services/http/http-browser.service.ts b/apps/deploy-web/src/services/http/http-browser.service.ts new file mode 100644 index 000000000..b22e87613 --- /dev/null +++ b/apps/deploy-web/src/services/http/http-browser.service.ts @@ -0,0 +1,8 @@ +import { browserEnvConfig } from "@src/config/browser-env.config"; +import { createServices } from "@src/services/http-factory/http-factory.service"; + +export const services = createServices(browserEnvConfig); + +export const userHttpService = services.user; +export const stripeService = services.stripe; +export const txHttpService = services.tx; diff --git a/apps/deploy-web/src/services/http/http-server.service.ts b/apps/deploy-web/src/services/http/http-server.service.ts new file mode 100644 index 000000000..08498ea7d --- /dev/null +++ b/apps/deploy-web/src/services/http/http-server.service.ts @@ -0,0 +1,4 @@ +import { serverEnvConfig } from "@src/config/server-env.config"; +import { createServices } from "@src/services/http-factory/http-factory.service"; + +export const services = createServices(serverEnvConfig); diff --git a/apps/deploy-web/src/services/http/http.service.ts b/apps/deploy-web/src/services/http/http.service.ts deleted file mode 100644 index 4f78f697a..000000000 --- a/apps/deploy-web/src/services/http/http.service.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { TxHttpService, UserHttpService } from "@akashnetwork/http-sdk"; -import { StripeService } from "@akashnetwork/http-sdk/src/stripe/stripe.service"; -import { event } from "nextjs-google-analytics"; - -import { browserEnvConfig } from "@src/config/browser-env.config"; -import { authService } from "@src/services/auth/auth.service"; -import { AnalyticsCategory, AnalyticsEvents } from "@src/types/analytics"; -import { customRegistry } from "@src/utils/customRegistry"; - -const apiConfig = { baseURL: browserEnvConfig.NEXT_PUBLIC_API_BASE_URL }; - -export const userHttpService = new UserHttpService(apiConfig); -export const stripeService = new StripeService(apiConfig); -export const txHttpService = new TxHttpService(customRegistry, apiConfig); - -userHttpService.interceptors.request.use(authService.withAnonymousUserHeader); -stripeService.interceptors.request.use(authService.withAnonymousUserHeader); -txHttpService.interceptors.request.use(authService.withAnonymousUserHeader); - -userHttpService.interceptors.response.use(response => { - if (response.config.url?.startsWith("/v1/anonymous-users") && response.config.method === "post" && response.status === 200) { - event(AnalyticsEvents.ANONYMOUS_USER_CREATED, { category: AnalyticsCategory.USER, label: "Anonymous User Created" }); - } - return response; -}); diff --git a/packages/http-sdk/src/template/template-http.service.ts b/packages/http-sdk/src/template/template-http.service.ts new file mode 100644 index 000000000..6f938f32d --- /dev/null +++ b/packages/http-sdk/src/template/template-http.service.ts @@ -0,0 +1,29 @@ +import type { AxiosRequestConfig } from "axios"; + +import { ApiHttpService } from "../api-http/api-http.service"; + +export interface TemplateOutput { + id: string; + name: string; + path: string; + logoUrl: string; + summary: string; + readme: string; + deploy: string; + persistentStorageEnabled: boolean; + guide: string; + githubUrl: string; + config: { + ssh?: boolean; + }; +} + +export class TemplateHttpService extends ApiHttpService { + constructor(config?: Pick) { + super(config); + } + + async findById(id: string) { + return await this.extractApiData(await this.get(`/v1/templates/${id}`)); + } +}