diff --git a/.github/workflows/api-contract-tests.yml b/.github/workflows/api-contract-tests.yml
index e3c2b3c6..f2ae524f 100644
--- a/.github/workflows/api-contract-tests.yml
+++ b/.github/workflows/api-contract-tests.yml
@@ -42,7 +42,7 @@ jobs:
sleep 15
- name: Print application logs
- run: docker-compose logs
+ run: docker compose logs
- name: Test connectivity
run: curl ${API_URL}
diff --git a/.github/workflows/backend-deploy-aws.yml b/.github/workflows/backend-deploy-aws.yml
new file mode 100644
index 00000000..4c66aa87
--- /dev/null
+++ b/.github/workflows/backend-deploy-aws.yml
@@ -0,0 +1,44 @@
+name: Deploy Backend to AWS
+
+on:
+ workflow_dispatch:
+
+jobs:
+ build:
+ defaults:
+ run:
+ working-directory: ./backend
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Login to DockerHub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Build and push Docker image
+ run: |
+ yarn docker:app:build
+ docker tag notes-app-be:latest ${{ secrets.DOCKERHUB_USERNAME }}/notes-app-be:latest
+ docker push ${{ secrets.DOCKERHUB_USERNAME }}/notes-app-be:latest
+ deploy:
+ needs: build
+ runs-on: ec2-runner-be
+ steps:
+ - name: Delete old be container
+ run: docker rm -f notes-app-be
+ - name: Delete old be image
+ run: docker image rm -f ${{ secrets.DOCKERHUB_USERNAME }}notes-app-fe
+ - name: Prune docker system
+ run: docker system prune -f
+ - name: Pull image from docker hub
+ run: docker pull ${{ secrets.DOCKERHUB_USERNAME }}/notes-app-be:latest
+ - name: Run docker container
+ run: docker run -e DATABASE_URL=${{ secrets.PROD_DATABASE_URL }} -e JWT_SECRET=${{ secrets.JWT_SECRET }} -d -p 5000:5000 --name notes-app-be ${{ secrets.DOCKERHUB_USERNAME }}/notes-app-be:latest
\ No newline at end of file
diff --git a/.github/workflows/backend-deploy.yml b/.github/workflows/backend-deploy.yml
deleted file mode 100644
index e9368820..00000000
--- a/.github/workflows/backend-deploy.yml
+++ /dev/null
@@ -1,14 +0,0 @@
-name: Backend Production Deploy
-
-on:
- workflow_dispatch:
-
-jobs:
- build:
- runs-on: ubuntu-latest
- steps:
- - name: Deploy to production
- uses: johnbeynon/render-deploy-action@v0.0.8
- with:
- service-id: ${{ secrets.MY_RENDER_SERVICE_ID_BE }}
- api-key: ${{ secrets.MY_RENDER_API_KEY }}
diff --git a/.github/workflows/backend-docker-push.yml b/.github/workflows/backend-docker-push.yml
deleted file mode 100644
index 3cd9525f..00000000
--- a/.github/workflows/backend-docker-push.yml
+++ /dev/null
@@ -1,39 +0,0 @@
-name: Docker Push Backend
-
-on:
- push:
- tags:
- - "be/*.*"
-defaults:
- run:
- working-directory: ./backend
-
-permissions: write-all
-
-jobs:
- build:
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v3
-
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3
-
- - name: Login to DockerHub
- uses: docker/login-action@v3
- with:
- username: ${{ secrets.DOCKERHUB_USERNAME }}
- password: ${{ secrets.DOCKERHUB_TOKEN }}
-
- - name: Extract version from tag
- id: extract_version
- run: echo "::set-output name=VERSION::$(echo ${GITHUB_REF/refs\/tags\//} | cut -d'/' -f2)"
-
- - name: Build and push Docker image
- env:
- IMAGE_TAG: ${{ steps.extract_version.outputs.VERSION }}
- run: |
- yarn docker:app:build
- docker tag notes-app-be:latest ${{ secrets.DOCKERHUB_USERNAME }}/notes-app-be:$IMAGE_TAG
- docker push ${{ secrets.DOCKERHUB_USERNAME }}/notes-app-be:$IMAGE_TAG
diff --git a/.github/workflows/backend-e2e-tests.yml b/.github/workflows/backend-e2e-tests.yml
index 720d235a..120a0a84 100644
--- a/.github/workflows/backend-e2e-tests.yml
+++ b/.github/workflows/backend-e2e-tests.yml
@@ -44,7 +44,7 @@ jobs:
sleep 15
- name: Print application logs
- run: docker-compose logs
+ run: docker compose logs
- name: Test connectivity
run: curl ${API_URL}
diff --git a/.github/workflows/frontend-deploy-aws.yml b/.github/workflows/frontend-deploy-aws.yml
new file mode 100644
index 00000000..1ee86907
--- /dev/null
+++ b/.github/workflows/frontend-deploy-aws.yml
@@ -0,0 +1,44 @@
+name: Deploy Frontend to AWS
+
+on:
+ workflow_dispatch:
+
+permissions: write-all
+
+jobs:
+ build:
+ defaults:
+ run:
+ working-directory: ./frontend
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Login to DockerHub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Build and push Docker image
+ run: |
+ docker build --build-arg REACT_APP_API_BASE_URL=${{ secrets.REACT_APP_API_BASE_URL }} -t notes-app-fe .
+ docker tag notes-app-fe:latest ${{ secrets.DOCKERHUB_USERNAME }}/notes-app-fe:latest
+ docker push ${{ secrets.DOCKERHUB_USERNAME }}/notes-app-fe:latest
+ deploy:
+ needs: build
+ runs-on: ec2-runner-fe
+ steps:
+ - name: Delete old fe container
+ run: docker rm -f notes-app-fe
+ - name: Prune docker system
+ run: docker system prune -f
+ - name: Pull image from docker hub
+ run: docker pull ${{ secrets.DOCKERHUB_USERNAME }}/notes-app-fe:latest
+ - name: Run docker container
+ run: docker run -e REACT_APP_API_BASE_URL=${{ secrets.REACT_APP_API_BASE_URL }} -d -p 3000:3000 --name notes-app-fe ${{ secrets.DOCKERHUB_USERNAME }}/notes-app-fe:latest
\ No newline at end of file
diff --git a/.github/workflows/frontend-deploy.yml b/.github/workflows/frontend-deploy.yml
deleted file mode 100644
index 1a658699..00000000
--- a/.github/workflows/frontend-deploy.yml
+++ /dev/null
@@ -1,14 +0,0 @@
-name: Frontend Production Deploy
-
-on:
- workflow_dispatch:
-
-jobs:
- build:
- runs-on: ubuntu-latest
- steps:
- - name: Deploy to production
- uses: johnbeynon/render-deploy-action@v0.0.8
- with:
- service-id: ${{ secrets.MY_RENDER_SERVICE_ID_FE }}
- api-key: ${{ secrets.MY_RENDER_API_KEY }}
diff --git a/.github/workflows/frontend-docker-push.yml b/.github/workflows/frontend-docker-push.yml
deleted file mode 100644
index a7b916e1..00000000
--- a/.github/workflows/frontend-docker-push.yml
+++ /dev/null
@@ -1,39 +0,0 @@
-name: Docker Push Frontend
-
-on:
- push:
- tags:
- - "fe/*.*" # Trigger on any tag creation
-defaults:
- run:
- working-directory: ./frontend
-
-permissions: write-all
-
-jobs:
- build:
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v3
-
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v1
-
- - name: Login to DockerHub
- uses: docker/login-action@v1
- with:
- username: ${{ secrets.DOCKERHUB_USERNAME }}
- password: ${{ secrets.DOCKERHUB_TOKEN }}
-
- - name: Extract version from tag
- id: extract_version
- run: echo "::set-output name=VERSION::$(echo ${GITHUB_REF/refs\/tags\//} | cut -d'/' -f2)"
-
- - name: Build and push Docker image
- env:
- IMAGE_TAG: ${{ steps.extract_version.outputs.VERSION }}
- run: |
- yarn docker:build
- docker tag notes-app-fe:latest ${{ secrets.DOCKERHUB_USERNAME }}/notes-app-fe:$IMAGE_TAG
- docker push ${{ secrets.DOCKERHUB_USERNAME }}/notes-app-fe:$IMAGE_TAG
diff --git a/.github/workflows/frontend-service-tests-deploy.yml b/.github/workflows/frontend-service-tests-deploy.yml
deleted file mode 100644
index 71fd69b7..00000000
--- a/.github/workflows/frontend-service-tests-deploy.yml
+++ /dev/null
@@ -1,70 +0,0 @@
-name: Frontend Service Tests - Deploy to Production
-
-on:
- push:
- branches: [ "main" ]
- paths:
- - "frontend/**"
-
-jobs:
- build:
- runs-on: ubuntu-latest
- env:
- API_URL: http://localhost:5000
- steps:
- - uses: actions/checkout@v4
-
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v1
-
- - name: Use Node.js 20.x
- uses: actions/setup-node@v4
- with:
- node-version: 20.x
-
- - name: Create .env file
- run: |
- cd ./backend
- echo "JWT_SECRET=${{ secrets.JWT_SECRET }}" > .env
-
- - name: Start service in docker
- run: |
- cd ./backend
- yarn docker:up
-
- - name: Wait for service to start
- run: |
- sleep 15
-
- - name: Test connectivity
- run: curl ${API_URL}
-
- - name: Install and start frontend
- run: |
- cd ./frontend
- yarn
- yarn build
- yarn start &
-
- - name: Install Playwright
- run: |
- cd ./playwright
- yarn
- yarn playwright install chromium
-
- - name: Run Playwright Tests against chromium
- run: |
- cd ./playwright
- yarn test:chromium
-
- - name: Stop service in docker
- run: |
- cd ./backend
- yarn docker:down
-
- - name: Deploy FE to production
- uses: johnbeynon/render-deploy-action@v0.0.8
-
- with:
- service-id: ${{ secrets.MY_RENDER_SERVICE_ID_FE }}
- api-key: ${{ secrets.MY_RENDER_API_KEY }}
diff --git a/backend/Dockerfile b/backend/Dockerfile
index 215ab822..6b0e390f 100644
--- a/backend/Dockerfile
+++ b/backend/Dockerfile
@@ -1,37 +1,40 @@
# Use an official Node.js runtime based on Alpine Linux as the base image
-FROM node:20-alpine
-
-# Install bash
-RUN apk add --no-cache bash
+FROM node:20-alpine as builder
# Set the working directory inside the container
WORKDIR /usr/src/app
-# Copy package.json and package-lock.json to the working directory
-COPY package.json yarn.lock ./
+# Install dependencies required for the build
+RUN apk add --no-cache bash openssl3
+
+# Copy package.json, package-lock.json, and TypeScript configuration files
+COPY package.json yarn.lock tsconfig.json ./
# Install the application dependencies
RUN yarn
-# Required for prisma
-RUN apk add openssl3
+# Copy the application code and other necessary files into the container
+COPY . ./
+COPY wait-for-it.sh wait-for-it.sh
-# Copy the TypeScript configuration files
-COPY tsconfig.json ./
+# Make the script executable and build the application
+RUN chmod +x wait-for-it.sh && \
+ npx prisma generate && \
+ yarn build
-# Copy the application code into the container
-COPY . .
+# Use a new, clean base image for the runtime
+FROM node:20-alpine
-COPY wait-for-it.sh wait-for-it.sh
+WORKDIR /usr/src/app
-# Make the script executable
-RUN chmod +x wait-for-it.sh
+# Install dependencies required for the build
+RUN apk add --no-cache bash
-# Primsa generate
-RUN npx prisma generate
+# Copy only the built artifacts and necessary files from the builder stage
+COPY --from=builder /usr/src/app/ ./
-# Build TypeScript code
-RUN yarn build
+# Copy the wait-for-it.sh script and make it executable
+RUN chmod +x wait-for-it.sh
# Expose the port that the application will run on
EXPOSE 5000
diff --git a/backend/package.json b/backend/package.json
index 2ce66ae8..c14fac3d 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -13,9 +13,9 @@
"build": "tsc",
"docker:app:build": "docker build -t notes-app-be .",
"docker:app:up": "docker run -p 5001:5001 notes-app-be",
- "docker:up": "docker-compose up -d",
- "docker:db:up": "docker-compose up postgres -d",
- "docker:down": "docker-compose down",
+ "docker:up": "docker compose up -d",
+ "docker:db:up": "docker compose up postgres -d",
+ "docker:down": "docker compose down",
"prisma": "prisma migrate dev --name init",
"seed": "ts-node prisma/seed.ts",
"lint": "eslint .",
diff --git a/backend/src/index.ts b/backend/src/index.ts
index aeb55d64..10c438a3 100644
--- a/backend/src/index.ts
+++ b/backend/src/index.ts
@@ -23,7 +23,7 @@ app.use('/', noteRoutes);
app.use('/', userRoutes);
app.use('/', loginRoutes);
-app.get('/health', (req, res) => {
+app.get('/api/health', (req, res) => {
res.json({ status: 'ok' });
});
diff --git a/backend/src/routes/userRoutes.ts b/backend/src/routes/userRoutes.ts
index 45ebffcd..ca2f7285 100644
--- a/backend/src/routes/userRoutes.ts
+++ b/backend/src/routes/userRoutes.ts
@@ -20,6 +20,23 @@ router.get('/api/users', authenticateToken, async (req, res) => {
}
});
+router.get('/api/user', authenticateToken, async (req, res) => {
+ // get id from decoded jwt token
+ const id = req.user.userId;
+
+ try {
+ const user = await prisma.user.findFirst({
+ where: { id },
+ });
+ // Remove password from the response
+ delete user.password;
+ res.json(user);
+ } catch (error) {
+ console.log('error', error);
+ res.status(500).send({ error: 'Oops, something went wrong' });
+ }
+});
+
router.post('/api/users', async (req, res) => {
const { email, password, username } = req.body;
diff --git a/backend/tests/e2e/notes.spec.ts b/backend/tests/e2e/notes.spec.ts
index 528f0994..19d4cf07 100644
--- a/backend/tests/e2e/notes.spec.ts
+++ b/backend/tests/e2e/notes.spec.ts
@@ -11,7 +11,7 @@ let createdID: number;
let token: string;
test('Health check', async () => {
- const response = await request(BASE_URL).get('/health');
+ const response = await request(BASE_URL).get('/api/health');
expect(response.status).toBe(200);
});
diff --git a/backend/tests/e2e/users.spec.ts b/backend/tests/e2e/users.spec.ts
index 1fdff6b7..c777ad74 100644
--- a/backend/tests/e2e/users.spec.ts
+++ b/backend/tests/e2e/users.spec.ts
@@ -7,6 +7,7 @@ config();
const BASE_URL = `${process.env.API_URL}`;
const USERS_URL = `${BASE_URL}/api/users`;
+const USER_URL = `${BASE_URL}/api/user`;
const username = faker.internet.userName().toLowerCase();
const email = faker.internet.email();
@@ -41,6 +42,16 @@ describe('Authenticated Flows', () => {
expect(getUsersResponse.body.length).toBeGreaterThan(0);
});
+ test('Get the user response', async () => {
+ const getUsersResponse = await request(USER_URL)
+ .get('/')
+ .set('Authorization', `Bearer ${token}`);
+ expect(getUsersResponse.status).toBe(200);
+ expect(getUsersResponse.body.username).toBe('Test User');
+ expect(getUsersResponse.body.email).toBe('helloitsdave@hotmail.com');
+ expect(getUsersResponse.body.password).toBeUndefined();
+ });
+
test("Username's should be unique", async () => {
const response = await request(USERS_URL)
.post('/')
diff --git a/backend/tests/integration/notes.spec.ts b/backend/tests/integration/notes.spec.ts
index 37ad95aa..83c75ad7 100644
--- a/backend/tests/integration/notes.spec.ts
+++ b/backend/tests/integration/notes.spec.ts
@@ -223,8 +223,8 @@ describe('Delete a note', () => {
});
describe('Health check', () => {
- test('GET /health', async ({}) => {
- const response = await request(app).get('/health');
+ test('GET /api/health', async ({}) => {
+ const response = await request(app).get('/api/health');
expect(response.status).toBe(200);
expect(response.body).toStrictEqual({ status: 'ok' });
});
diff --git a/backend/tests/integration/users.spec.ts b/backend/tests/integration/users.spec.ts
index 5a8f0c09..1f24be0a 100644
--- a/backend/tests/integration/users.spec.ts
+++ b/backend/tests/integration/users.spec.ts
@@ -56,6 +56,39 @@ describe('Get Users', () => {
});
});
+describe('Get user', () => {
+ test('GET user info for existing user', async () => {
+ prisma.user.findFirst.mockResolvedValue({
+ id: 'ccf89a7e-b941-4f17-bbe0-4e0c8b2cd272',
+ username: 'Dave',
+ password: 'check',
+ email: 'testing@backend.com',
+ createdAt: new Date('2024-02-05T23:33:42.252Z'),
+ updatedAt: new Date('2024-02-05T23:33:42.252Z'),
+ });
+
+ const response = await request(app).get('/api/user');
+ expect(response.status).toBe(200);
+ expect(response.body).toStrictEqual({
+ id: 'ccf89a7e-b941-4f17-bbe0-4e0c8b2cd272',
+ username: 'Dave',
+ email: 'testing@backend.com',
+ createdAt: '2024-02-05T23:33:42.252Z',
+ updatedAt: '2024-02-05T23:33:42.252Z',
+ });
+ });
+ test('Network Error', async ({}) => {
+ prisma.user.findFirst.mockImplementation(() => {
+ throw new Error('Test error');
+ });
+ const response = await request(app).get('/api/user');
+ expect(response.status).toBe(500);
+ expect(response.body).toStrictEqual({
+ error: 'Oops, something went wrong',
+ });
+ });
+});
+
describe('Create User', () => {
test('POST with email, username and password', async ({}) => {
prisma.user.create.mockResolvedValue({
@@ -137,7 +170,7 @@ describe('Create User', () => {
describe('Delete User', () => {
test('DELETE with id', async ({}) => {
prisma.user.delete.mockResolvedValue({
- id: 'gcf89a7e-b941-4f17-bbe0-4e0c8b2cd272',
+ id: 'ccf89a7e-b941-4f17-bbe0-4e0c8b2cd272',
username: 'Dave',
password: 'check',
email: null,
diff --git a/frontend/src/App.css b/frontend/src/App.css
index 28f0df29..71bd60e9 100644
--- a/frontend/src/App.css
+++ b/frontend/src/App.css
@@ -38,7 +38,8 @@ body::before {
}
.action-header {
- width: 30%;
+ width: 40%;
+ flex-basis: auto;
}
}
diff --git a/frontend/src/NoteApp.tsx b/frontend/src/NoteApp.tsx
index ada3e011..59ac3832 100644
--- a/frontend/src/NoteApp.tsx
+++ b/frontend/src/NoteApp.tsx
@@ -4,8 +4,16 @@ import './App.css';
import NoteFormModal from './components/NoteFormModal';
import NoteGrid from './components/NoteGrid';
import Spinner from './components/Spinner';
+import UserModal from './components/UserModal';
import type NoteType from './types/note';
-import { postNote, patchNote, getNotes, removeNote } from './api/apiService';
+import type UserType from './types/user';
+import {
+ postNote,
+ patchNote,
+ getNotes,
+ removeNote,
+ getUser,
+} from './api/apiService';
export interface LogoutProps {
onLogout: () => void;
@@ -17,9 +25,12 @@ const NoteApp: React.FC
- Updated {new Date(note.updatedAt ?? '').toLocaleDateString()} + Updated: {dayjs(note.updatedAt).format('DD MMM YY')}