From 990f8370290e65e3cd7d9e2d78659f2bc1eb6839 Mon Sep 17 00:00:00 2001 From: unifolio0 Date: Fri, 20 Feb 2026 15:38:28 +0900 Subject: [PATCH 1/2] =?UTF-8?q?chore:=20=ED=94=84=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cleanup-preview.yml | 36 +++++++ .github/workflows/deploy-preview.yml | 119 +++++++++++++++++++++ src/constants/debate_template.ts | 4 +- src/page/DebateVotePage/DebateVotePage.tsx | 4 +- src/routes/routes.tsx | 45 ++++---- src/util/arrayEncoding.ts | 4 +- src/util/googleAuth.ts | 8 +- src/vite-env.d.ts | 1 + vite.config.ts | 1 + 9 files changed, 198 insertions(+), 24 deletions(-) create mode 100644 .github/workflows/cleanup-preview.yml create mode 100644 .github/workflows/deploy-preview.yml diff --git a/.github/workflows/cleanup-preview.yml b/.github/workflows/cleanup-preview.yml new file mode 100644 index 00000000..54d810df --- /dev/null +++ b/.github/workflows/cleanup-preview.yml @@ -0,0 +1,36 @@ +name: Cleanup-Preview + +on: + pull_request: + types: [closed] + branches: ['develop'] + +jobs: + cleanup: + runs-on: ubuntu-latest + environment: PREVIEW_ENV + permissions: + pull-requests: write + + steps: + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_PREVIEW_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_PREVIEW_SECRET_ACCESS_KEY }} + aws-region: ap-northeast-2 + + - name: Delete Preview from S3 + run: | + aws s3 rm s3://${{ secrets.AWS_PREVIEW_BUCKET_NAME }}/pr-${{ github.event.pull_request.number }}/ --recursive + + - name: Comment Cleanup Notice + uses: actions/github-script@v7 + with: + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body: '๐Ÿงน Preview ๋ฐฐํฌ๊ฐ€ ์ •๋ฆฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.' + }); diff --git a/.github/workflows/deploy-preview.yml b/.github/workflows/deploy-preview.yml new file mode 100644 index 00000000..3d8274ef --- /dev/null +++ b/.github/workflows/deploy-preview.yml @@ -0,0 +1,119 @@ +name: Deploy-Preview + +on: + pull_request: + types: [opened, synchronize, reopened] + branches: ['develop'] + +concurrency: + group: preview-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + deploy-preview: + runs-on: ubuntu-latest + environment: PREVIEW_ENV + permissions: + pull-requests: write + contents: read + + steps: + - name: Setup NodeJS + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Setup .env + run: | + echo "${{ vars.ENV }}" > .env + echo "${{ secrets.ENV }}" >> .env + echo "VITE_BASE_PATH=/pr-${{ github.event.pull_request.number }}" >> .env + + - name: Install Dependencies + run: npm ci + + - name: Build Preview + run: npm run build + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_PREVIEW_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_PREVIEW_SECRET_ACCESS_KEY }} + aws-region: ap-northeast-2 + + - name: Deploy to S3 Preview Bucket + run: | + aws s3 sync ./dist s3://${{ secrets.AWS_PREVIEW_BUCKET_NAME }}/pr-${{ github.event.pull_request.number }}/ --delete + + - name: Deploy OAuth Redirect Handler + run: | + cat > /tmp/oauth-handler.html << 'OAUTH_EOF' + + + Redirecting... + + + + + OAUTH_EOF + aws s3 cp /tmp/oauth-handler.html s3://${{ secrets.AWS_PREVIEW_BUCKET_NAME }}/oauth --content-type "text/html" + + - name: Invalidate CloudFront Cache + run: | + aws cloudfront create-invalidation \ + --distribution-id ${{ secrets.AWS_PREVIEW_CLOUDFRONT_ID }} \ + --paths "/pr-${{ github.event.pull_request.number }}/*" "/oauth" + + - name: Comment Preview URL on PR + uses: actions/github-script@v7 + with: + script: | + const prNumber = context.payload.pull_request.number; + const url = `https://preview.debate-timer.com/pr-${prNumber}/`; + + const body = `## ๐Ÿš€ Preview ๋ฐฐํฌ ์™„๋ฃŒ! + + | ํ™˜๊ฒฝ | URL | + |-----|-----| + | Preview | [์—ด๊ธฐ](${url}) | + | API | Dev ํ™˜๊ฒฝ | + + > PR์ด ๋‹ซํžˆ๋ฉด ์ž๋™์œผ๋กœ ์ •๋ฆฌ๋ฉ๋‹ˆ๋‹ค.`; + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + }); + + const existing = comments.find(c => + c.user.login === 'github-actions[bot]' && + c.body.includes('Preview ๋ฐฐํฌ ์™„๋ฃŒ') + ); + + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body + }); + } diff --git a/src/constants/debate_template.ts b/src/constants/debate_template.ts index bac13ddf..961c0bce 100644 --- a/src/constants/debate_template.ts +++ b/src/constants/debate_template.ts @@ -16,7 +16,9 @@ import { DebateTemplate } from '../type/type'; function createTableShareUrl(encodeData: string): string { const baseUrl = import.meta.env.VITE_SHARE_BASE_URL || window.location.origin; const normalizedBaseUrl = baseUrl.replace(/\/+$/, ''); - return `${normalizedBaseUrl}/share?data=${encodeData}`; + const basePath = import.meta.env.VITE_BASE_PATH; + const pathPrefix = basePath && basePath !== '/' ? basePath : ''; + return `${normalizedBaseUrl}${pathPrefix}/share?data=${encodeData}`; } interface DebateTemplateList { ONE: DebateTemplate[]; diff --git a/src/page/DebateVotePage/DebateVotePage.tsx b/src/page/DebateVotePage/DebateVotePage.tsx index 4f7924da..d1deff7f 100644 --- a/src/page/DebateVotePage/DebateVotePage.tsx +++ b/src/page/DebateVotePage/DebateVotePage.tsx @@ -33,7 +33,9 @@ export default function DebateVotePage() { const isArgsValid = isPollIdValid && isTableIdValid; const voteUrl = useMemo(() => { - return `${baseUrl}/vote/${pollId}`; + const basePath = import.meta.env.VITE_BASE_PATH; + const pathPrefix = basePath && basePath !== '/' ? basePath : ''; + return `${baseUrl}${pathPrefix}/vote/${pollId}`; }, [baseUrl, pollId]); const handleGoToResult = () => { diff --git a/src/routes/routes.tsx b/src/routes/routes.tsx index e19ae4ee..d6c9a899 100644 --- a/src/routes/routes.tsx +++ b/src/routes/routes.tsx @@ -102,28 +102,33 @@ const protectedAppRoutes = appRoutes.map((route) => ({ ), })); -const router = createBrowserRouter([ +const router = createBrowserRouter( + [ + { + element: ( + <> + + + + ), + children: [ + { + path: '/', + element: , + children: protectedAppRoutes, // ๊ธฐ๋ณธ ์–ธ์–ด(ko) ๋ผ์šฐํŠธ + }, + { + path: ':lang', // ๋‹ค๋ฅธ ์–ธ์–ด ๋ผ์šฐํŠธ + element: , + children: protectedAppRoutes, + }, + ], + }, + ], { - element: ( - <> - - - - ), - children: [ - { - path: '/', - element: , - children: protectedAppRoutes, // ๊ธฐ๋ณธ ์–ธ์–ด(ko) ๋ผ์šฐํŠธ - }, - { - path: ':lang', // ๋‹ค๋ฅธ ์–ธ์–ด ๋ผ์šฐํŠธ - element: , - children: protectedAppRoutes, - }, - ], + basename: import.meta.env.VITE_BASE_PATH || '/', }, -]); +); // ๋ผ์šฐํŠธ ๋ณ€๊ฒฝ ์‹œ Google Analytics ์ด๋ฒคํŠธ ์ „์†ก router.subscribe(({ location }) => { diff --git a/src/util/arrayEncoding.ts b/src/util/arrayEncoding.ts index 515d4db1..a96c30bc 100644 --- a/src/util/arrayEncoding.ts +++ b/src/util/arrayEncoding.ts @@ -28,7 +28,9 @@ export function createTableShareUrl( const resolvedBaseUrl = baseUrl && baseUrl.trim() !== '' ? baseUrl : window.location.origin; const normalizedBaseUrl = resolvedBaseUrl.replace(/\/+$/, ''); - return `${normalizedBaseUrl}/share?data=${encoded}`; + const basePath = import.meta.env.VITE_BASE_PATH; + const pathPrefix = basePath && basePath !== '/' ? basePath : ''; + return `${normalizedBaseUrl}${pathPrefix}/share?data=${encoded}`; } export function extractTableShareUrl(url: string): DebateTableData | null { diff --git a/src/util/googleAuth.ts b/src/util/googleAuth.ts index 1d4104c9..5d3ee844 100644 --- a/src/util/googleAuth.ts +++ b/src/util/googleAuth.ts @@ -7,12 +7,18 @@ export const oAuthLogin = () => { throw new Error('OAuth ์ •๋ณด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.'); } - const params = { + const params: Record = { client_id: import.meta.env.VITE_GOOGLE_O_AUTH_CLIENT_ID, redirect_uri: import.meta.env.VITE_GOOGLE_O_AUTH_REDIRECT_URI, response_type: 'code', scope: 'openid profile email', }; + + const basePath = import.meta.env.VITE_BASE_PATH; + if (basePath && basePath !== '/') { + params.state = basePath; + } + const queryString = new URLSearchParams(params).toString(); const googleOAuthUrl = `${import.meta.env.VITE_GOOGLE_O_AUTH_REQUEST_URL}?${queryString}`; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index bd3d95eb..17433776 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -2,6 +2,7 @@ interface ImportMetaEnv { readonly VITE_MOCK_API: string; + readonly VITE_BASE_PATH: string; } interface ImportMeta { diff --git a/vite.config.ts b/vite.config.ts index ce11360d..4489551d 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -6,6 +6,7 @@ const viteConfig = defineViteConfig(({ mode }) => { const env = loadEnv(mode, process.cwd(), ''); return { + base: env.VITE_BASE_PATH || '/', plugins: [react()], server: { proxy: { From d689f8a93496798ac5f9164eec1e2c08ab1b0635 Mon Sep 17 00:00:00 2001 From: unifolio0 Date: Fri, 20 Feb 2026 15:58:54 +0900 Subject: [PATCH 2/2] =?UTF-8?q?chore:=20=ED=94=84=EB=A6=AC=EB=B7=B0?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=96=B8=EC=96=B4=20=EB=B3=80=ED=99=98?= =?UTF-8?q?=EC=9D=B4=20=EC=A0=81=EC=9A=A9=EB=90=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/i18n.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n.ts b/src/i18n.ts index a7da32f7..a11585d5 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -19,7 +19,7 @@ i18n // ๋ฒˆ์—ญ ํŒŒ์ผ์„ ๋ถˆ๋Ÿฌ์˜ฌ ์œ„์น˜ backend: { - loadPath: '/locales/{{lng}}/translation.json', + loadPath: `${import.meta.env.VITE_BASE_PATH || ''}/locales/{{lng}}/translation.json`, }, // React์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ๋•Œ์˜ ์˜ต์…˜