Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
REACT_APP_GOOGLE_MAPS_API_KEY=--your-google-maps-api-key--
VITE_API_SERVER_URL=http://localhost:3000
VITE_API_SERVER_URL=http://localhost:3000/api
63 changes: 63 additions & 0 deletions .github/workflows/deploy-dev.yml
Original file line number Diff line number Diff line change
@@ -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:
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_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: |
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 "/*"
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
14 changes: 12 additions & 2 deletions capacitor.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,18 @@ 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
},
plugins: {
CapacitorHttp: {
enabled: true,
},
},
};

export default config;
6 changes: 3 additions & 3 deletions src/api/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 } },
},
};
2 changes: 1 addition & 1 deletion src/api/services/auth-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ENDPOINTS } from "../constants";

export class AuthService {
async login(credentials: AuthDto, setTokens: (tokens: ResponseTokenDto) => void) {
const tokens = await transport.post<ResponseTokenDto>(ENDPOINTS.auth.signin.url, credentials);
const tokens = await transport.post<ResponseTokenDto>(ENDPOINTS.auth.signin.url(), credentials);
setTokens(tokens);
}
}
14 changes: 7 additions & 7 deletions src/api/transport/base-transoport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,28 @@ export abstract class BaseTransport {
abstract patch<T>(endpoint: string, data: object, options: TransportOptions): Promise<T>;
abstract delete<T>(endpoint: string, options: TransportOptions): Promise<T>;

public async useEndpoint<T>(endpoint: TEndpoint, data?: object | null): Promise<T> {
public async useEndpoint<T>(endpoint: TEndpoint, data?: object | null, params?: Record<string, string>): Promise<T> {
switch (endpoint.method) {
case 'GET':
return this.get<T>(endpoint.url, endpoint.options);
return this.get<T>(endpoint.url(params), endpoint.options);
case 'POST':
return this.post<T>(endpoint.url, data ?? null, endpoint.options);
return this.post<T>(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<T>(endpoint.url, data, endpoint.options);
return this.put<T>(endpoint.url(params), data, endpoint.options);
}
case 'PATCH': {
if (!data) {
console.error('Data is required for PATCH request, default to {}');
data = {};
}
return this.patch<T>(endpoint.url, data, endpoint.options);
return this.patch<T>(endpoint.url(params), data, endpoint.options);
}
case 'DELETE':
return this.delete<T>(endpoint.url, endpoint.options);
return this.delete<T>(endpoint.url(params), endpoint.options);
}
}

Expand All @@ -56,7 +56,7 @@ export abstract class BaseTransport {

protected async refreshTokens(): Promise<ResponseTokenDto> {
const tokens = JSON.parse(localStorage.getItem('auth') || '{}') as ResponseTokenDto;
const newTokens = await this.post<ResponseTokenDto>(ENDPOINTS.auth.refresh.url, null, {
const newTokens = await this.post<ResponseTokenDto>(ENDPOINTS.auth.refresh.url(), null, {
customHeaders: { Authorization: `Bearer ${tokens.refreshToken}` },
});
localStorage.setItem('auth', JSON.stringify(newTokens));
Expand Down
4 changes: 2 additions & 2 deletions src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export type TransportOptions = {
};

export type TEndpoint = {
url: string;
url: (params?: Record<string, string>) => string;
options: TransportOptions;
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
};
Expand All @@ -17,5 +17,5 @@ export interface ITransport {
put: <T>(endpoint: string, data: object, options: TransportOptions) => Promise<T>;
patch: <T>(endpoint: string, data: object, options: TransportOptions) => Promise<T>;
delete: <T>(endpoint: string, options: TransportOptions) => Promise<T>;
useEndpoint: <T>(endpoint: TEndpoint, data: object | null) => Promise<T>;
useEndpoint: <T>(endpoint: TEndpoint, data: object | null, params?: Record<string, string>) => Promise<T>;
}