From 7b3df712ec8855176574490a1ab172ffc02a0d87 Mon Sep 17 00:00:00 2001 From: Ihor S Date: Sun, 7 Sep 2025 17:05:27 +0200 Subject: [PATCH 1/4] allow http --- capacitor.config.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/capacitor.config.ts b/capacitor.config.ts index 1014045..ed5712f 100644 --- a/capacitor.config.ts +++ b/capacitor.config.ts @@ -6,8 +6,13 @@ const config: CapacitorConfig = { webDir: 'dist', android: { minWebViewVersion: 55, - backgroundColor: "#00000000", - }, + backgroundColor: '#00000000', + allowMixedContent: true, // remove when using https + }, + server: { + androidScheme: 'http', // remove when using https + cleartext: true, // remove when using https + }, }; export default config; From 9adec548b3acf528e7afa0176b4a85d257bed28c Mon Sep 17 00:00:00 2001 From: Ihor S Date: Sun, 7 Sep 2025 18:19:08 +0200 Subject: [PATCH 2/4] enable CapacitorHttp plugin to tackle cors problem --- capacitor.config.ts | 5 +++++ src/api/constants.ts | 6 +++--- src/api/services/auth-service.ts | 2 +- src/api/transport/base-transoport.ts | 14 +++++++------- src/api/types.ts | 4 ++-- 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/capacitor.config.ts b/capacitor.config.ts index ed5712f..71c1d4e 100644 --- a/capacitor.config.ts +++ b/capacitor.config.ts @@ -13,6 +13,11 @@ const config: CapacitorConfig = { androidScheme: 'http', // remove when using https cleartext: true, // remove when using https }, + plugins: { + CapacitorHttp: { + enabled: true, + }, + }, }; export default config; diff --git a/src/api/constants.ts b/src/api/constants.ts index f79bfbd..856337d 100644 --- a/src/api/constants.ts +++ b/src/api/constants.ts @@ -5,10 +5,10 @@ export const BASE_URL = import.meta.env.VITE_API_SERVER_URL; export const ENDPOINTS: TEndpoints = { auth: { - signin: { url: 'auth/signin', method: 'POST', options: { auth: false } }, - refresh: { url: 'auth/refresh', method: 'POST', options: { auth: false } }, + signin: { url: () => 'auth/signin', method: 'POST', options: { auth: false } }, + refresh: { url: () => 'auth/refresh', method: 'POST', options: { auth: false } }, }, users: { - getAll: { url: 'users', method: 'GET', options: { auth: true } }, + getAll: { url: () => 'users', method: 'GET', options: { auth: true } }, }, }; diff --git a/src/api/services/auth-service.ts b/src/api/services/auth-service.ts index a698c5c..fd42ba2 100644 --- a/src/api/services/auth-service.ts +++ b/src/api/services/auth-service.ts @@ -5,7 +5,7 @@ import { ENDPOINTS } from "../constants"; export class AuthService { async login(credentials: AuthDto, setTokens: (tokens: ResponseTokenDto) => void) { - const tokens = await transport.post(ENDPOINTS.auth.signin.url, credentials); + const tokens = await transport.post(ENDPOINTS.auth.signin.url(), credentials); setTokens(tokens); } } diff --git a/src/api/transport/base-transoport.ts b/src/api/transport/base-transoport.ts index b088f88..a07b200 100644 --- a/src/api/transport/base-transoport.ts +++ b/src/api/transport/base-transoport.ts @@ -10,28 +10,28 @@ export abstract class BaseTransport { abstract patch(endpoint: string, data: object, options: TransportOptions): Promise; abstract delete(endpoint: string, options: TransportOptions): Promise; - public async useEndpoint(endpoint: TEndpoint, data?: object | null): Promise { + public async useEndpoint(endpoint: TEndpoint, data?: object | null, params?: Record): Promise { switch (endpoint.method) { case 'GET': - return this.get(endpoint.url, endpoint.options); + return this.get(endpoint.url(params), endpoint.options); case 'POST': - return this.post(endpoint.url, data ?? null, endpoint.options); + return this.post(endpoint.url(params), data ?? null, endpoint.options); case 'PUT': { if (!data) { console.error('Data is required for PUT request, default to {}'); data = {}; } - return this.put(endpoint.url, data, endpoint.options); + return this.put(endpoint.url(params), data, endpoint.options); } case 'PATCH': { if (!data) { console.error('Data is required for PATCH request, default to {}'); data = {}; } - return this.patch(endpoint.url, data, endpoint.options); + return this.patch(endpoint.url(params), data, endpoint.options); } case 'DELETE': - return this.delete(endpoint.url, endpoint.options); + return this.delete(endpoint.url(params), endpoint.options); } } @@ -56,7 +56,7 @@ export abstract class BaseTransport { protected async refreshTokens(): Promise { const tokens = JSON.parse(localStorage.getItem('auth') || '{}') as ResponseTokenDto; - const newTokens = await this.post(ENDPOINTS.auth.refresh.url, null, { + const newTokens = await this.post(ENDPOINTS.auth.refresh.url(), null, { customHeaders: { Authorization: `Bearer ${tokens.refreshToken}` }, }); localStorage.setItem('auth', JSON.stringify(newTokens)); diff --git a/src/api/types.ts b/src/api/types.ts index 6410a7f..cdd01b2 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -4,7 +4,7 @@ export type TransportOptions = { }; export type TEndpoint = { - url: string; + url: (params?: Record) => string; options: TransportOptions; method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; }; @@ -17,5 +17,5 @@ export interface ITransport { put: (endpoint: string, data: object, options: TransportOptions) => Promise; patch: (endpoint: string, data: object, options: TransportOptions) => Promise; delete: (endpoint: string, options: TransportOptions) => Promise; - useEndpoint: (endpoint: TEndpoint, data: object | null) => Promise; + useEndpoint: (endpoint: TEndpoint, data: object | null, params?: Record) => Promise; } \ No newline at end of file From 800879ab4df994d9d9939e83cfc1dfb12d58a7f9 Mon Sep 17 00:00:00 2001 From: Ihor S Date: Mon, 8 Sep 2025 18:56:35 +0200 Subject: [PATCH 3/4] test cicd --- .env.example | 2 +- .github/workflows/deploy-dev.yml | 63 ++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/deploy-dev.yml diff --git a/.env.example b/.env.example index c072ad2..3123d7b 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,2 @@ REACT_APP_GOOGLE_MAPS_API_KEY=--your-google-maps-api-key-- -VITE_API_SERVER_URL=http://localhost:3000 \ No newline at end of file +VITE_API_SERVER_URL=http://localhost:3000/api \ No newline at end of file diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml new file mode 100644 index 0000000..daef171 --- /dev/null +++ b/.github/workflows/deploy-dev.yml @@ -0,0 +1,63 @@ +name: Deploy UI to AWS +on: + pull_request: + branches: + - master + types: + - closed + +jobs: + deploy-ui: + if: github.event.pull_request.merged + runs-on: ubuntu-latest + env: + CODE_BUCKET: ${{vars.CODE_BUCKET}} + AWS_REGION: ${{ vars.AWS_REGION }} + VITE_GOOGLE_MAPS_API_KEY: ${{ vars.VITE_GOOGLE_MAPS_API_KEY }} + VITE_API_SERVER_URL: ${{ vars.VITE_API_SERVER_URL }} + CLOUDFRONT_DISTRIBUTION_ID: ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Cache ui files + id: cache-ui + uses: actions/cache@v4 + env: + cache-name: cache-ui-files + with: + path: . + key: ${{ hashFiles('package*.json') }}-${{ hashFiles('src/**/*') }} + + - if: ${{ steps.cache-ui.outputs.cache-hit == 'true' }} + name: Check ui changes + continue-on-error: true + run: echo 'No ui changes found. Skip ui build and deployment.' + + - if: ${{ steps.cache-ui.outputs.cache-hit != 'true' }} + name: Build + run: | + export VITE_GOOGLE_MAPS_API_KEY="${{ vars.VITE_GOOGLE_MAPS_API_KEY }}" + export VITE_API_SERVER_URL="${{ vars.VITE_API_SERVER_URL }}" + npm ci + npm run build + + - if: ${{ steps.cache-ui.outputs.cache-hit != 'true' }} + name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{secrets.AWS_ACCESS_KEY_ID}} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{vars.AWS_REGION}} + + - if: ${{ steps.cache-ui.outputs.cache-hit != 'true' }} + name: Sync with S3 + run: | + aws s3 sync ./dist s3://$CODE_BUCKET --delete + + - if: ${{ steps.cache-ui.outputs.cache-hit != 'true' }} + name: Invalidate cloudfront + run: | + # Send SSM command to invalidate cloudfront + aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DISTRIBUTION_ID --paths "/*" From ede01c33e61f55c87e33f612fb3cc3c60a7ae12e Mon Sep 17 00:00:00 2001 From: Ihor S Date: Mon, 8 Sep 2025 19:24:21 +0200 Subject: [PATCH 4/4] cicd --- .github/workflows/deploy-dev.yml | 6 +++--- README.md | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 README.md diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index daef171..14cd48c 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -11,9 +11,11 @@ jobs: if: github.event.pull_request.merged runs-on: ubuntu-latest env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + VITE_GOOGLE_MAPS_API_KEY: ${{ secrets.VITE_GOOGLE_MAPS_API_KEY }} CODE_BUCKET: ${{vars.CODE_BUCKET}} AWS_REGION: ${{ vars.AWS_REGION }} - VITE_GOOGLE_MAPS_API_KEY: ${{ vars.VITE_GOOGLE_MAPS_API_KEY }} VITE_API_SERVER_URL: ${{ vars.VITE_API_SERVER_URL }} CLOUDFRONT_DISTRIBUTION_ID: ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }} @@ -38,8 +40,6 @@ jobs: - if: ${{ steps.cache-ui.outputs.cache-hit != 'true' }} name: Build run: | - export VITE_GOOGLE_MAPS_API_KEY="${{ vars.VITE_GOOGLE_MAPS_API_KEY }}" - export VITE_API_SERVER_URL="${{ vars.VITE_API_SERVER_URL }}" npm ci npm run build diff --git a/README.md b/README.md new file mode 100644 index 0000000..6f75cd7 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# deployment + +## before deployment, a relevant infrastructure must be created (s3 bucket, cloudfront distribution, route53 record) + +See ../api/README.md for details how to create it (it is created with api's infrastructure setup cdk script) + +## ci/cd variables + +before setup ci/cd, you need to know and set up the following variables + +- secrets.VITE_GOOGLE_MAPS_API_KEY +- secrets.AWS_ACCESS_KEY_ID +- secrets.AWS_SECRET_ACCESS_KEY +- vars.CODE_BUCKET +- vars.AWS_REGION +- vars.VITE_API_SERVER_URL +- vars.CLOUDFRONT_DISTRIBUTION_ID