diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 0000000..18f8bd7 --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -0,0 +1,59 @@ +on: + workflow_dispatch: + push: + branches: + - main + +name: Delpoy to Google Cloud Run +env: + PROJECT_ID: ${{ secrets.GCP_PROJECT }} + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup Cloud SDK + uses: google-github-actions/setup-gcloud@v0.2.0 + with: + project_id: ${{ env.PROJECT_ID }} + service_account_key: ${{ secrets.GCP_SA_KEY }} + export_default_credentials: true # Set to true to authenticate the Cloud Run action + + - name: Authorize Docker Push + run: gcloud auth configure-docker + + - name: Build Server Container + run: docker build -t gcr.io/${{ env.PROJECT_ID }}/server:${{ github.sha }} server + + - name: Build Client Container + run: docker build -t gcr.io/${{ env.PROJECT_ID }}/client:${{ github.sha }} client + + - name: Push Server Container + run: docker push gcr.io/${{ env.PROJECT_ID }}/server:${{ github.sha }} + + - name: Push Client Container + run: docker push gcr.io/${{ env.PROJECT_ID }}/client:${{ github.sha }} + + - name: Deploy Server + id: deploy_server + uses: google-github-actions/deploy-cloudrun@v0.4.0 + with: + service: server + image: gcr.io/${{ env.PROJECT_ID }}/server:${{ github.sha }} + + - name: Show Server Output + run: echo ${{ steps.deploy_server.outputs.url }} + + - name: Deploy Client + id: deploy_client + uses: google-github-actions/deploy-cloudrun@v0.4.0 + with: + service: client + image: gcr.io/${{ env.PROJECT_ID }}/client:${{ github.sha }} + + - name: Show Client Output + run: echo ${{ steps.deploy_client.outputs.url }} + diff --git a/client/.env.template b/client/.env.template deleted file mode 100644 index df8e625..0000000 --- a/client/.env.template +++ /dev/null @@ -1,3 +0,0 @@ -# Replace the [value] with valid link to api server. -# If api server is run locally, it usually is (or something like) http://localhost:8080 -REACT_APP_API_SERVER_URL=[value] diff --git a/client/Dockerfile b/client/Dockerfile new file mode 100644 index 0000000..534a7aa --- /dev/null +++ b/client/Dockerfile @@ -0,0 +1,10 @@ +FROM node:12-alpine AS builder +WORKDIR /app +COPY . . +RUN yarn install --production +RUN yarn build + +FROM nginx:alpine +COPY ./nginx.conf /etc/nginx/templates/default.conf.template +COPY --from=builder /app/build /usr/share/nginx/html + diff --git a/client/nginx.conf b/client/nginx.conf new file mode 100644 index 0000000..5f15b2d --- /dev/null +++ b/client/nginx.conf @@ -0,0 +1,15 @@ +server { + listen ${PORT}; + listen [::]:${PORT}; + server_name localhost; + + location /api/ { + proxy_pass ${API_SERVER_URL}/api/; + } + + location / { + root /usr/share/nginx/html; + try_files $uri /index.html; + } +} + diff --git a/client/src/apiBudget.js b/client/src/apiBudget.js index 6fbdd35..9a0959c 100644 --- a/client/src/apiBudget.js +++ b/client/src/apiBudget.js @@ -1,5 +1,5 @@ export async function getBalances() { - const res = await fetch(new URL('/api/transaction/balance', process.env.REACT_APP_API_SERVER_URL)); + const res = await fetch('/api/transaction/balance'); return res.json(); } @@ -12,7 +12,7 @@ export async function createTransaction(description, amount) { 'amount': parseFloat(amount) } - await fetch(new URL('/api/transaction', process.env.REACT_APP_API_SERVER_URL), { + await fetch('/api/transaction', { body: JSON.stringify(transaction), method: 'POST', headers: { @@ -22,8 +22,8 @@ export async function createTransaction(description, amount) { } export async function deleteTransaction(id) { - await fetch(new URL(`/api/transaction/${id}`, process.env.REACT_APP_API_SERVER_URL), { + await fetch(`/api/transaction/${id}`, { method: 'DELETE' }); -} \ No newline at end of file +} diff --git a/client/src/apiMember.js b/client/src/apiMember.js index 473dea4..36777ef 100644 --- a/client/src/apiMember.js +++ b/client/src/apiMember.js @@ -1,5 +1,5 @@ export async function getMembers() { - const res = await fetch(new URL('/api/member', process.env.REACT_APP_API_SERVER_URL)); + const res = await fetch('/api/member'); return res.json(); } @@ -11,7 +11,7 @@ export async function createMember(name) { 'expiryDate': dateStr } - await fetch(new URL('/api/member', process.env.REACT_APP_API_SERVER_URL), { + await fetch('/api/member', { body: JSON.stringify(member), method: 'POST', headers: { @@ -21,14 +21,14 @@ export async function createMember(name) { } export async function activateMember(id) { - await fetch(new URL(`api/member/${id}`, process.env.REACT_APP_API_SERVER_URL), { + await fetch(`/api/member/${id}`, { method: 'PUT' }); } export async function deleteMember(id) { - await fetch(new URL(`/api/member/${id}`, process.env.REACT_APP_API_SERVER_URL), { + await fetch(`/api/member/${id}`, { method: 'DELETE' }); -} \ No newline at end of file +} diff --git a/ddl.sql b/ddl.sql new file mode 100644 index 0000000..ad50c1d --- /dev/null +++ b/ddl.sql @@ -0,0 +1,17 @@ +-- Initialize Database Schema + +create table member ( +id bigint not null auto_increment, +name varchar(50) not null, +expiry_date varchar(12) not null, +primary key (id) +); + +create table transaction ( +id bigint not null auto_increment, +date varchar(12) not null, +description varchar(50) not null, +amount double(19,4) not null, +primary key (id) +); + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..06bb219 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,40 @@ +version: "3" +services: + web: + build: ./client + ports: + - 3000:3000 + environment: + PORT: 3000 + API_SERVER_URL: http://backend:4000 + depends_on: + - backend + backend: + build: ./server + environment: + SERVER_PORT: 4000 + SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/rich-neighborhoods + SPRING_DATASOURCE_USERNAME: rich-neighborhoods + SPRING_DATASOURCE_PASSWORD: weroinvoiwenr + SPRING_JPA_HIBERNATE_DDL-AUTO: update + SPRING_JPA_PROPERTIES_HIBERNATE_DIALECT: org.hibernate.dialect.MySQL8Dialect + ports: + - 4000:4000 + restart: always + depends_on: + - db + db: + image: mysql + environment: + MYSQL_USER: rich-neighborhoods + MYSQL_PASSWORD: weroinvoiwenr + MYSQL_DATABASE: rich-neighborhoods + MYSQL_RANDOM_ROOT_PASSWORD: "true" + ports: + - 3306:3306 + volumes: + - dbdata:/var/lib/mysql + - ./ddl.sql:/docker-entrypoint-initdb.d/ddl.sql:ro +volumes: + dbdata: + diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000..6199485 --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1,11 @@ +FROM gradle:7-jdk11 AS builder +WORKDIR /project +COPY . . +RUN gradle build + +FROM openjdk:11 +WORKDIR /jars +COPY --from=builder /project/build . + +CMD java -jar /jars/**/*.jar + diff --git a/server/src/test/java/com/richneighborhoods0420/server/ServerApplicationTests.java b/server/src/test/java/com/richneighborhoods0420/server/ServerApplicationTests.java deleted file mode 100644 index 9325426..0000000 --- a/server/src/test/java/com/richneighborhoods0420/server/ServerApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.richneighborhoods0420.server; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class ServerApplicationTests { - - @Test - void contextLoads() { - } - -}