diff --git a/backend/eslint.config.js b/backend/eslint.config.js index e5eb2558..6dfc1ada 100644 --- a/backend/eslint.config.js +++ b/backend/eslint.config.js @@ -1,34 +1,14 @@ -import js from '@eslint/js' -import jest from 'eslint-plugin-jest' -import globals from 'globals' +import globals from "globals"; +import pluginJs from "@eslint/js"; +/** @type {import('eslint').Linter.Config[]} */ export default [ - { ignores: ['dist'] }, - { - files: ['**/*.{js,ts}'], - languageOptions: { - ecmaVersion: 'latest', - globals: globals.node, - parserOptions: { - ecmaVersion: 'latest', - ecmaFeatures: { jsx: true }, - sourceType: 'module', - }, - }, - rules: { - ...js.configs.recommended.rules, - }, - }, - { - files: ['**/__tests__/**/*.test.{js,ts}'], - plugins: { - jest, - }, - languageOptions: { - globals: jest.environments.globals.globals - }, - rules: { - ...jest.configs.recommended.rules, - } - } -] + { languageOptions: { globals: { ...globals.node, ...globals.jest } } }, + pluginJs.configs.recommended, + { + rules: { + camelcase: ["warn", { properties: "never", ignoreDestructuring: false }], + "max-len": ["warn", { code: 140 }], + }, + }, +]; \ No newline at end of file diff --git a/backend/index.js b/backend/index.js index 0284af06..673ef126 100644 --- a/backend/index.js +++ b/backend/index.js @@ -1,9 +1,9 @@ import cors from "cors"; import dotenv from "dotenv"; import express from "express"; -import authRoutes from "./src/routes/authRoutes.js"; -import logRoutes from "./src/routes/logRoutes.js"; -import transcriptionRoutes from "./src/routes/transcriptionRoutes.js"; +import authRoutes from "./src/routes/auth-route.js"; +import logbookRoutes from "./src/routes/logbooks-route.js"; +import transcriptionRoutes from "./src/routes/transcription-route.js"; import fileUpload from "express-fileupload"; dotenv.config(); @@ -20,7 +20,7 @@ app.use(fileUpload()); //Routes app.use("/api/auth", authRoutes); -app.use("/api/log", logRoutes); +app.use("/api/logbooks", logbookRoutes); app.use("/api/transcriptions", transcriptionRoutes); app.listen(PORT, () => { diff --git a/backend/package-lock.json b/backend/package-lock.json index 6f56cc3a..e3630110 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -15,6 +15,7 @@ "dotenv": "^16.4.5", "express": "^4.21.0", "express-fileupload": "^1.5.1", + "form-data": "^4.0.1", "jsonwebtoken": "^9.0.2", "supabase": "^1.207.9" }, @@ -3026,20 +3027,6 @@ "proxy-from-env": "^1.1.0" } }, - "node_modules/axios/node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -4475,6 +4462,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", diff --git a/backend/package.json b/backend/package.json index 64457144..9077ec29 100644 --- a/backend/package.json +++ b/backend/package.json @@ -6,7 +6,7 @@ "scripts": { "build": "babel index.js -d dist", "dev": "nodemon server", - "lint": "eslint .", + "lint": "eslint", "start": "node index.js", "test": "jest" }, @@ -20,6 +20,7 @@ "dotenv": "^16.4.5", "express": "^4.21.0", "express-fileupload": "^1.5.1", + "form-data": "^4.0.1", "jsonwebtoken": "^9.0.2", "supabase": "^1.207.9" }, diff --git a/backend/src/middlewares/__tests__/auth.test.js b/backend/src/middlewares/__tests__/auth.test.js deleted file mode 100644 index d8d52d4b..00000000 --- a/backend/src/middlewares/__tests__/auth.test.js +++ /dev/null @@ -1,3 +0,0 @@ -test("empty test", () => { - expect(true).toBe(true) -}) \ No newline at end of file diff --git a/backend/src/middlewares/auth.js b/backend/src/middlewares/auth.js index f758065c..b8d71ea2 100644 --- a/backend/src/middlewares/auth.js +++ b/backend/src/middlewares/auth.js @@ -16,15 +16,12 @@ const auth = async (req, res, next) => { }) req.supabase = supabase; } else { - console.error("No token: Authentication Denied"); return res.status(401).json({ message: "No token: Authentication Denied" }); } next(); } catch (err) { - console.error("Invalid token, authentication denied:", err.message); return res.status(400).json({ message: `Invalid token, authentication denied: ${err.message}`}); } }; -export default auth; - +export default auth; \ No newline at end of file diff --git a/backend/src/routes/authRoutes.js b/backend/src/routes/auth-route.js similarity index 95% rename from backend/src/routes/authRoutes.js rename to backend/src/routes/auth-route.js index b0474aac..a0f746f7 100644 --- a/backend/src/routes/authRoutes.js +++ b/backend/src/routes/auth-route.js @@ -3,7 +3,6 @@ import auth from "../middlewares/auth.js"; const router = express.Router(); -//auth test router.post("/check", auth, async (req, res) => { res.json({ message: "Auth Check Ok"}); }) diff --git a/backend/src/routes/logRoutes.js b/backend/src/routes/logRoutes.js deleted file mode 100644 index e6955084..00000000 --- a/backend/src/routes/logRoutes.js +++ /dev/null @@ -1,17 +0,0 @@ -import express from "express"; -import auth from "../middlewares/auth.js"; -import insertTable from "../services/cardiacSurgeryAdultService.js"; - -const router = express.Router(); - -router.post("/cardiacSurgeryAdultService", auth, async (req, res) => { - try { - const result = await insertTable(req, res); - res.json(result); - } catch (error) { - console.error("Caught Error:", error.message); - res.status(500).json({ message: error.message }); - } -}); - -export default router; diff --git a/backend/src/routes/logbooks-route.js b/backend/src/routes/logbooks-route.js new file mode 100644 index 00000000..5f7f4e59 --- /dev/null +++ b/backend/src/routes/logbooks-route.js @@ -0,0 +1,61 @@ +import express from "express"; +import auth from "../middlewares/auth.js"; +import { createLogbook, getUserLogbooks, getLogbook, createLog, getLogbookLogs, getLog } from "../services/logbooks-service.js"; + +const router = express.Router(); + +router.post("", auth, async (req, res) => { + const logbook = await createLogbook(req); + if (logbook.error) { + res.status(500).json({ error: logbook.error }); + } else { + res.status(201).json({ data: logbook }); + } +}); + +router.get("", auth, async (req, res) => { + const userLogbooks = await getUserLogbooks(req); + if (userLogbooks.error) { + res.status(500).json({ error: userLogbooks.error }); + } else { + res.status(200).json({ data: userLogbooks }); + } +}); + +router.get("/:logbookID", auth, async (req, res) => { + const logbook = await getLogbook(req); + if (logbook.error) { + res.status(500).json({ error: logbook.error }); + } else { + res.status(200).json({ data: logbook }); + } +}); + +router.post("/:logbookID/logs", auth, async (req, res) => { + const log = await createLog(req); + if (log.error) { + res.status(500).json({ error: log.error }); + } else { + res.status(201).json({ data: log }); + } +}); + +router.get("/:logbookID/logs", auth, async (req, res) => { + const logbookLogs = await getLogbookLogs(req); + if (logbookLogs.error) { + res.status(500).json({ error: logbookLogs.error }); + } else { + res.status(200).json({ data: logbookLogs }); + } +}); + +router.get("/:logbookID/logs/:logID", auth, async (req, res) => { + const log = await getLog(req); + if (log.error) { + res.status(500).json({ error: log.error }); + } else { + res.status(200).json({ data: log }); + } +}); + +export default router; \ No newline at end of file diff --git a/backend/src/routes/test/auth-route.test.js b/backend/src/routes/test/auth-route.test.js new file mode 100644 index 00000000..06224af9 --- /dev/null +++ b/backend/src/routes/test/auth-route.test.js @@ -0,0 +1,3 @@ +test("empty test", () => { + expect(true).toBe(true); +}); diff --git a/backend/src/routes/test/logbooks-route.test.js b/backend/src/routes/test/logbooks-route.test.js new file mode 100644 index 00000000..06224af9 --- /dev/null +++ b/backend/src/routes/test/logbooks-route.test.js @@ -0,0 +1,3 @@ +test("empty test", () => { + expect(true).toBe(true); +}); diff --git a/backend/src/routes/transcriptionRoutes.js b/backend/src/routes/transcription-route.js similarity index 100% rename from backend/src/routes/transcriptionRoutes.js rename to backend/src/routes/transcription-route.js diff --git a/backend/src/services/cardiacSurgeryAdultService.js b/backend/src/services/cardiacSurgeryAdultService.js deleted file mode 100644 index 5cc3ce69..00000000 --- a/backend/src/services/cardiacSurgeryAdultService.js +++ /dev/null @@ -1,76 +0,0 @@ - -async function insertTable(req) { - try{ - const supabase = req.supabase; - - const { - case_no, - patient_id, - type, - surgeon, - or_date, - age, - sex, - reason, - hpi, - social, - pmhx, - meds, - allergies, - exam, - veins, - allen_test, - pulses, - invx, - cxr, - ct, - cath, - surgical_plan, - operative_notes, - post_op_course, - learning_points - } = req.body; - - const error = await supabase.schema("user_info").from("cardiac_surgery_adult_log") - .insert({ - case_no: case_no, - patient_id: patient_id, - type: type, - surgeon: surgeon, - or_date: or_date, - age: age, - sex: sex, - reason: reason, - hpi: hpi, - social: social, - pmhx: pmhx, - meds: meds, - allergies: allergies, - exam: exam, - veins: veins, - allen_test: allen_test, - pulses: pulses, - invx: invx, - cxr: cxr, - ct: ct, - cath: cath, - surgical_plan: surgical_plan, - operative_notes: operative_notes, - post_op_course: post_op_course, - learning_points: learning_points }); - - if (error.error) { - console.log(error); - console.error("Insert Error:", error.error.message); - throw new Error("Failed to insert data: " + error.error.message); - } - - return {message: "Log Successful"}; - } catch (error) { - console.error("Error in insertTable:", error.message); - throw new Error(error.message); - } -} - -export default insertTable; - diff --git a/backend/src/services/logbooks-service.js b/backend/src/services/logbooks-service.js new file mode 100644 index 00000000..6f30499e --- /dev/null +++ b/backend/src/services/logbooks-service.js @@ -0,0 +1,97 @@ +import getLogbookType from "../utils/get-logbook-type.js"; +import getTable from "../utils/get-table.js"; +import insertTable from "../utils/insert-table.js"; +import parseUserID from "../utils/parse-user-id.js"; + +export async function createLogbook(req) { + const supabase = req.supabase; + const body = req.body; + const logbook = await insertTable(supabase, "logbooks", body); + return logbook; +} + +export async function getUserLogbooks(req) { + try { + const supabase = req.supabase; + const token = req.header("Authorization")?.split(" ")[1]; + const userID = parseUserID(token); + if (userID.error) { + throw new Error(userID.error) + } + const userLogbooks = await getTable(supabase, "logbooks", "user_id", userID, "collection"); + return userLogbooks; + } catch (error) { + return { error: error.message }; + } +} + +export async function getLogbook(req) { + try { + const supabase = req.supabase; + const { logbookID } = req.params; + const logbook = await getTable(supabase, "logbooks", "id", logbookID, "resource"); + if (typeof logbook == "undefined") { + throw new Error(`logbook ${logbookID} does not exist`); + } + return logbook; + } catch (error) { + return { error: error.message }; + } +} + +export async function createLog(req) { + try { + const supabase = req.supabase; + const { logbookID } = req.params; + let body = req.body; + body["logbook_id"] = logbookID; + const logbookType = await getLogbookType(logbookID, supabase); + if (logbookType.error) { + throw new Error(logbookType.error); + } + if (body["type"] !== logbookType) { + throw new Error(`log type '${body["type"]}' does not match logbook type '${logbookType}'`); + } + switch (body["type"]) { + case "adult_cardiac_logs": + return await insertTable(supabase, "adult_cardiac_logs", body); + default: + throw new Error(`log and logbook type '${body["type"]}' are invalid`); + } + } catch (error) { + return { error: error.message }; + } +} + +export async function getLogbookLogs(req) { + try { + const supabase = req.supabase; + const { logbookID } = req.params; + const logbookType = await getLogbookType(logbookID, supabase); + if (logbookType.error) { + throw new Error(logbookType.error); + } + const logbookLogs = await getTable(supabase, logbookType, "logbook_id", logbookID, "collection"); + return logbookLogs; + } catch (error) { + return { error: error.message }; + } +} + +export async function getLog(req) { + try { + const supabase = req.supabase; + const { logbookID, logID } = req.params; + const logbookType = await getLogbookType(logbookID, supabase); + if (logbookType.error) { + throw new Error(logbookType.error); + } + const log = await getTable(supabase, logbookType, "id", logID, "resource"); + if (typeof log == "undefined") { + throw new Error(`log ${logID} does not exist`); + } + return log; + } catch (error) { + return { error: error.message }; + } +} diff --git a/backend/src/utils/get-logbook-type.js b/backend/src/utils/get-logbook-type.js new file mode 100644 index 00000000..a402b79a --- /dev/null +++ b/backend/src/utils/get-logbook-type.js @@ -0,0 +1,13 @@ +export default async function getLogbookType(logbookID, supabase) { + try { + const { data, error } = await supabase.from("logbooks").select().eq("id", logbookID); + if (data.length == 0) { + throw new Error(`logbook ${logbookID} does not exist`); + } else if (error) { + throw new Error(error.message); + } + return data[0]["type"]; + } catch (error) { + return { error: error.message }; + } +} \ No newline at end of file diff --git a/backend/src/utils/get-table.js b/backend/src/utils/get-table.js new file mode 100644 index 00000000..4371cb90 --- /dev/null +++ b/backend/src/utils/get-table.js @@ -0,0 +1,22 @@ +export default async function getTable(supabase, table, param, value, type) { + try { + let data, error; + if (param == null && value == null) { + ({ data, error } = await supabase.from(table).select()); + } else if (param !== null && value !== null) { + ({ data, error } = await supabase.from(table).select().eq(param, value)); + } else { + throw new Error(`${param == null ? "param" : "value"} is empty at getTable`); + } + if (error) { + throw new Error(error.message); + } + if (type === "collection") { + return data + } else if (type === "resource") { + return data[0] + } + } catch (error) { + return { error: error.message }; + } +} \ No newline at end of file diff --git a/backend/src/utils/insert-table.js b/backend/src/utils/insert-table.js new file mode 100644 index 00000000..796f3929 --- /dev/null +++ b/backend/src/utils/insert-table.js @@ -0,0 +1,12 @@ +export default async function insertTable(supabase, table, values) { + try { + const { data, error } = await supabase.from(table).insert(values).select(); + if (error) { + throw new Error(error.message); + } else { + return data[0]; + } + } catch (error) { + return { error: error.message }; + } +} \ No newline at end of file diff --git a/backend/src/utils/parse-user-id.js b/backend/src/utils/parse-user-id.js new file mode 100644 index 00000000..50012650 --- /dev/null +++ b/backend/src/utils/parse-user-id.js @@ -0,0 +1,9 @@ +export default function parseUserID(token) { + try { + const parts = token.split("."); + const decodedPayload = JSON.parse(atob(parts[1])); + return decodedPayload["sub"]; + } catch (error) { + return { error: error.message }; + } +} \ No newline at end of file