Skip to content

Commit

Permalink
Merge branch 'master' into VPS-45/daisy-ui-tabs
Browse files Browse the repository at this point in the history
  • Loading branch information
harbassan authored Oct 21, 2024
2 parents 10d188f + 3f60996 commit 6df30d1
Show file tree
Hide file tree
Showing 66 changed files with 1,217 additions and 1,047 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/deploy-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 14
node-version: 20

- name: Create env file
run: echo ${{ secrets.FRONTEND_ENVFILE }} | base64 --decode > .env
Expand Down Expand Up @@ -47,7 +47,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 14
node-version: 20

- name: Create env file
run: echo ${{ secrets.BACKEND_ENVFILE }} | base64 --decode > gae_env.yaml
Expand Down
26 changes: 26 additions & 0 deletions .github/workflows/deploy.production.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Fly Deploy Production

on: [workflow_dispatch]

jobs:
deploy-web:
name: Deploy Web
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: superfly/flyctl-actions/setup-flyctl@master
- run: flyctl deploy --remote-only --config fly.production.toml --build-arg VITE_FIREBASE_PROJECT_ID=${{ secrets.VITE_FIREBASE_PROJECT_ID }} --build-arg VITE_REACT_APP_API_KEY=${{ secrets.VITE_REACT_APP_API_KEY }} --build-arg VITE_REACT_APP_AUTH_DOMAIN=${{ secrets.VITE_REACT_APP_AUTH_DOMAIN }} --build-arg VITE_REACT_APP_STORAGE_BUCKET=${{ secrets.VITE_REACT_APP_STORAGE_BUCKET }} --build-arg VITE_REACT_APP_MESSAGING_SENDER_ID=${{ secrets.VITE_REACT_APP_MESSAGING_SENDER_ID }} --build-arg VITE_REACT_APP_APP_ID=${{ secrets.VITE_REACT_APP_APP_ID }} --build-arg VITE_REACT_APP_MEASUREMENT_ID=${{ secrets.VITE_REACT_APP_MEASUREMENT_ID }} --build-arg VITE_REACT_APP_SERVER_URL=${{ secrets.VITE_REACT_APP_SERVER_URL_PRODUCTION }}
working-directory: ./frontend
env:
FLY_API_TOKEN: ${{ secrets.FLY_PRODUCTION_API_TOKEN }}

deploy-api:
name: Deploy Api
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: superfly/flyctl-actions/setup-flyctl@master
- run: flyctl deploy --remote-only --config fly.production.toml
working-directory: ./backend
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_PRODUCTION_API_TOKEN }}
29 changes: 29 additions & 0 deletions .github/workflows/deploy.staging.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Fly Deploy Staging

on:
push:
branches:
- master

jobs:
deploy-web:
name: Deploy Web
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: superfly/flyctl-actions/setup-flyctl@master
- run: flyctl deploy --remote-only --config fly.staging.toml --build-arg VITE_FIREBASE_PROJECT_ID=${{ secrets.VITE_FIREBASE_PROJECT_ID }} --build-arg VITE_REACT_APP_API_KEY=${{ secrets.VITE_REACT_APP_API_KEY }} --build-arg VITE_REACT_APP_AUTH_DOMAIN=${{ secrets.VITE_REACT_APP_AUTH_DOMAIN }} --build-arg VITE_REACT_APP_STORAGE_BUCKET=${{ secrets.VITE_REACT_APP_STORAGE_BUCKET }} --build-arg VITE_REACT_APP_MESSAGING_SENDER_ID=${{ secrets.VITE_REACT_APP_MESSAGING_SENDER_ID }} --build-arg VITE_REACT_APP_APP_ID=${{ secrets.VITE_REACT_APP_APP_ID }} --build-arg VITE_REACT_APP_MEASUREMENT_ID=${{ secrets.VITE_REACT_APP_MEASUREMENT_ID }} --build-arg VITE_REACT_APP_SERVER_URL=${{ secrets.VITE_REACT_APP_SERVER_URL_STAGING }}
working-directory: ./frontend
env:
FLY_API_TOKEN: ${{ secrets.FLY_STAGING_API_TOKEN }}

deploy-api:
name: Deploy Api
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: superfly/flyctl-actions/setup-flyctl@master
- run: flyctl deploy --remote-only --config fly.staging.toml
working-directory: ./backend
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_STAGING_API_TOKEN }}
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# VPS

# Virtual Patient Simulator (VPS)
This project aims to provide Medical and Health Science students at the University of Auckland with a tool that supports interactive and immersive education through virtual patient scenarios.

![VPS home page](/images/vps-hero.png)

This project was associated with The University of Auckland SOFTENG 761 but since 2022, is being developed by WDCC project teams. The repo located here for this project is a bare clone of the original repo, which no longer exists.

# Live deployments
Expand Down
1 change: 1 addition & 0 deletions backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
41 changes: 41 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# syntax = docker/dockerfile:1
# Adjust NODE_VERSION as desired
ARG NODE_VERSION=18.19.0
FROM node:${NODE_VERSION}-slim as base

LABEL fly_launch_runtime="Node.js"

# Node.js app lives here
WORKDIR /app

# Set production environment
ARG YARN_VERSION=1.22.21
RUN npm install -g yarn@$YARN_VERSION --force

# Throw-away build stage to reduce size of final image
FROM base as install

# Install packages needed to build node modules
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential node-gyp pkg-config python-is-python3

# Install node modules
COPY --link package.json yarn.lock ./
RUN yarn install --frozen-lockfile

# Copy application code
COPY --link . .

FROM base as build

COPY --from=install /app /app

# Final stage for app image
FROM base

# Copy built application
COPY --from=build /app /app

# Start the server by default, this can be overwritten at runtime
EXPOSE 5000
CMD [ "yarn", "run", "start" ]
18 changes: 18 additions & 0 deletions backend/fly.production.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
app = 'wdcc-vps-api'
primary_region = 'syd'

[build]
dockerfile = "Dockerfile"

[http_service]
internal_port = 5000
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 0
processes = ['app']

[[vm]]
cpu_kind = 'shared'
cpus = 1
memory_mb = 1024
18 changes: 18 additions & 0 deletions backend/fly.staging.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
app = 'wdcc-vps-api-staging'
primary_region = 'syd'

[build]
dockerfile = "Dockerfile"

[http_service]
internal_port = 5000
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 0
processes = ['app']

[[vm]]
cpu_kind = 'shared'
cpus = 1
memory_mb = 1024
10 changes: 5 additions & 5 deletions backend/src/db/daos/scenarioDao.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ const retrieveScenarios = async (scenarioIds) => {
*/
const retrieveRoleList = async (scenarioId) => {
const scenario = await Scenario.findById(scenarioId);
return scenario.roleList;
return scenario?.roleList ?? [];
};

/**
Expand Down Expand Up @@ -162,12 +162,12 @@ const deleteScenario = async (scenarioId) => {

export {
createScenario,
retrieveScenarioList,
deleteScenario,
retrieveRoleList,
retrieveScenario,
retrieveScenarioList,
retrieveScenarios,
retrieveRoleList,
updateScenario,
deleteScenario,
updateDurations,
updateRoleList,
updateScenario,
};
2 changes: 1 addition & 1 deletion backend/src/db/db-connect.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ const DEFAULT_CONNECTION_STRING = `mongodb+srv://${process.env.MONGODB_USER}:${p
export default function connectToDatabase(
connectionString = DEFAULT_CONNECTION_STRING
) {
return mongoose.connect(connectionString);
return mongoose.connect(connectionString, { useNewUrlParser: true });
}
6 changes: 4 additions & 2 deletions backend/src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import express from "express";
import cors from "cors";
import { join } from "path";
import { join, dirname } from "path";
import { fileURLToPath } from "url";
import connectToDatabase from "./db/db-connect.js";

// Setup our routes.
Expand All @@ -18,8 +19,9 @@ app.use(express.json());
app.use("/", routes);
app.use(errorHandler);

const __dirname = dirname(fileURLToPath(import.meta.url));
// Make the "public" folder available statically
app.use(express.static(join(import.meta.dirname, "../public")));
app.use(express.static(join(__dirname, "../public")));

// Serve up the frontend's "build" directory, if we're running in production mode.
if (process.env.NODE_ENV === "production") {
Expand Down
18 changes: 18 additions & 0 deletions backend/src/middleware/validScenarioId.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import mongoose from "mongoose";

const HTTP_BAD_REQUEST = 400;

/**
* Checks if the scenarioId is valid
*/
export default async function validScenarioId(req, res, next) {
if (
req.params?.scenarioId &&
!mongoose.isValidObjectId(req.params.scenarioId)
) {
res.status(HTTP_BAD_REQUEST).json({ error: "Invalid scenario ID." });
return;
}

next();
}
32 changes: 17 additions & 15 deletions backend/src/routes/api/group.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import { Router } from "express";

import {
createGroup,
getCurrentScene,
getGroup,
createGroup,
getGroupByScenarioId,
} from "../../db/daos/groupDao.js";

import { retrieveRoleList, updateRoleList } from "../../db/daos/scenarioDao.js";
import Group from "../../db/models/group.js";

import validScenarioId from "../../middleware/validScenarioId.js";

const router = Router();

const HTTP_OK = 200;
const HTTP_CONFLICT = 409;
const HTTP_NO_CONTENT = 204;
const HTTP_NOT_FOUND = 404;
const HTTP_BAD_REQUEST = 400;

Check warning on line 20 in backend/src/routes/api/group.js

View workflow job for this annotation

GitHub Actions / Run linters backend

'HTTP_BAD_REQUEST' is assigned a value but never used

// get the groups assigned to a scenario
router.get("/scenario/:scenarioId", async (req, res) => {
Expand All @@ -39,6 +41,19 @@ router.get("/path/:groupId", async (req, res) => {
}
});

// get a group by its id
router.get("/retrieve/:groupId", async (req, res) => {
const { groupId } = req.params;
const group = await getGroup(groupId);
if (!group) {
return res.status(HTTP_NOT_FOUND).json({ error: "Group not found" });
}
return res.status(HTTP_OK).json(group);
});

export default router;

router.use("/:scenarioId", validScenarioId);
// create a new group
router.post("/:scenarioId", async (req, res) => {
const { groupList, roleList } = req.body;
Expand Down Expand Up @@ -98,20 +113,7 @@ router.post("/:scenarioId", async (req, res) => {

router.get("/:scenarioId/roleList", async (req, res) => {
const { scenarioId } = req.params;

const roleList = await retrieveRoleList(scenarioId);

res.status(HTTP_OK).json(roleList);
});

// get a group by its id
router.get("/retrieve/:groupId", async (req, res) => {
const { groupId } = req.params;
const group = await getGroup(groupId);
if (!group) {
return res.status(HTTP_NOT_FOUND).json({ error: "Group not found" });
}
return res.status(HTTP_OK).json(group);
});

export default router;
13 changes: 11 additions & 2 deletions backend/src/routes/api/scenario.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { Router } from "express";
import auth from "../../middleware/firebaseAuth.js";
import scenarioAuth from "../../middleware/scenarioAuth.js";
import validScenarioId from "../../middleware/validScenarioId.js";

import {
createScenario,
retrieveScenarioList,
updateScenario,
deleteScenario,
retrieveScenario,
retrieveScenarioList,
updateDurations,
updateScenario,
} from "../../db/daos/scenarioDao.js";

import { retrieveAssignedScenarioList } from "../../db/daos/userDao.js";
Expand Down Expand Up @@ -48,8 +50,15 @@ router.post("/", async (req, res) => {
});

// Apply scenario auth middleware
router.use("/:scenarioId", validScenarioId);
router.use("/:scenarioId", scenarioAuth);

// Get a scenario by id.
router.get("/:scenarioId", async (req, res) => {
const scenario = await retrieveScenario(req.params.scenarioId);
res.status(HTTP_OK).json(scenario);
});

// Update a scenario by a user
router.put("/:scenarioId", async (req, res) => {
const { name, duration } = req.body;
Expand Down
8 changes: 5 additions & 3 deletions backend/src/routes/api/scene.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ import { Router } from "express";

import {
createScene,
retrieveSceneList,
retrieveScene,
updateScene,
deleteScene,
duplicateScene,
incrementVisisted,
retrieveScene,
retrieveSceneList,
updateScene,
} from "../../db/daos/sceneDao.js";
import auth from "../../middleware/firebaseAuth.js";
import scenarioAuth from "../../middleware/scenarioAuth.js";
import validScenarioId from "../../middleware/validScenarioId.js";

const router = Router({ mergeParams: true });

Expand All @@ -20,6 +21,7 @@ const HTTP_NOT_FOUND = 404;
// Apply auth middleware to all routes below this point
router.use(auth);
// Apply scenario auth middleware
router.use(validScenarioId);
router.use(scenarioAuth);

// Get scene infromation
Expand Down
5 changes: 2 additions & 3 deletions backend/src/routes/api/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,8 @@ router.post(
handle(async (req, res) => {
const email = req?.body?.email || "";
if (
!(email.split("@").length > 1) &&
!allowedDomains.has(email.split("@")[1]) &&
!allowedEmails.has(email)
email.split("@").length <= 1 ||
(!allowedDomains.has(email.split("@")[1]) && !allowedEmails.has(email))
) {
throw new HttpError("Sign in with your UoA account", STATUS.FORBIDDEN);
}
Expand Down
3 changes: 3 additions & 0 deletions frontend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dist
node_modules
.env
1 change: 1 addition & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

# production
/build
/dist

# environment
.env
Expand Down
Loading

0 comments on commit 6df30d1

Please sign in to comment.