lysine commited on
Commit
ee7d090
·
0 Parent(s):

first commit

Browse files
.dockerignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ node_modules
2
+ npm-debug.log
.env.example ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ PORT=7860
2
+ VITE_SERVER_URL=http://localhost:7860/
.eslintignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ node_modules
2
+ .DS_Store
3
+ dist
4
+ dist-ssr
5
+ *.local
6
+ .eslintcache
.eslintrc.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "env": {
3
+ "browser": true,
4
+ "es2021": true,
5
+ "node": true
6
+ },
7
+ "extends": [
8
+ "eslint:recommended",
9
+ "plugin:@typescript-eslint/recommended",
10
+ "prettier"
11
+ ],
12
+ "parser": "@typescript-eslint/parser",
13
+ "parserOptions": {
14
+ "ecmaVersion": 12,
15
+ "sourceType": "module"
16
+ },
17
+ "plugins": ["@typescript-eslint"],
18
+ "rules": {
19
+ "@typescript-eslint/no-explicit-any": "off"
20
+ }
21
+ }
.github/workflows/hugging-face.yml ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Sync to Hugging Face hub
2
+ on:
3
+ push:
4
+ branches: [main]
5
+
6
+ # to run this workflow manually from the Actions tab
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ sync-to-hub:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v3
14
+ with:
15
+ fetch-depth: 0
16
+ - name: Push to hub
17
+ env:
18
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
19
+ run: git push https://lysine:$HF_TOKEN@huggingface.co/spaces/lysine/med main
.gitignore ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ node_modules
2
+ .DS_Store
3
+ dist
4
+ dist-ssr
5
+ *.local
6
+ .eslintcache
7
+ .env
.prettierignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ node_modules
2
+ .DS_Store
3
+ dist
4
+ dist-ssr
5
+ *.local
6
+ .eslintcache
.prettierrc.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "singleQuote": true,
3
+ "arrowParens": "avoid",
4
+ "tabWidth": 2,
5
+ "trailingComma": "es5"
6
+ }
Dockerfile ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ARG VITE_SERVER_URL
2
+
3
+ FROM node:18
4
+
5
+ # Set up a new user named "user" with user ID 1000
6
+ RUN useradd -o -u 1000 user
7
+
8
+ # Switch to the "user" user
9
+ USER user
10
+
11
+ # Set home to the user's home directory
12
+ ENV HOME=/home/user \
13
+ PATH=/home/user/.local/bin:$PATH
14
+
15
+ # Set the working directory to the user's home directory
16
+ WORKDIR $HOME/app
17
+
18
+ # Copy the current directory contents into the container at $HOME/app setting the owner to the user
19
+ COPY --chown=user . $HOME/app
20
+
21
+ # Install npm dependencies
22
+ RUN npm install
23
+
24
+ # Build client and server
25
+ RUN export VITE_SERVER_URL=$MODEL_REPO_NAME && npm run build
26
+
27
+ EXPOSE 7860
28
+ CMD [ "npm", "run", "start" ]
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Henry Lin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: med
3
+ emoji: ➕
4
+ colorFrom: red
5
+ colorTo: purple
6
+ sdk: docker
7
+ pinned: true
8
+ ---
9
+
10
+ # med
11
+
12
+ A central hub for all the clinical database apps.
13
+
14
+ [![Open med on HF Spaces](https://huggingface.co/datasets/huggingface/badges/raw/main/open-in-hf-spaces-xl-dark.svg)](https://lysine-med.hf.space/)
15
+
16
+ ## License
17
+
18
+ This project is licensed under the MIT License - see the LICENSE file for details.
19
+
20
+ ## Acknowledgments
21
+
22
+ * Thanks [Hugging Face](https://huggingface.co/) for hosting the app online for free.
index.html ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/src/app/favicon.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Clinical Database</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/app/main.tsx"></script>
12
+ </body>
13
+ </html>
nodemon.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "watch": ["src"],
3
+ "ext": "js,jsx,ts,tsx,json",
4
+ "exec": "ts-node --esm --project tsconfig.server.json --experimental-specifier-resolution=node ./src/server"
5
+ }
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "med",
3
+ "version": "0.1.0",
4
+ "description": "A central hub linking to all clinical databases.",
5
+ "private": true,
6
+ "type": "module",
7
+ "scripts": {
8
+ "dev": "concurrently \"npm run server:dev\" \"npm run client:dev\"",
9
+ "client:dev": "vite",
10
+ "server:dev": "nodemon",
11
+ "server:build": "tsc --project tsconfig.server.json",
12
+ "client:build": "vite build",
13
+ "build": "npm run server:build && npm run client:build",
14
+ "serve": "vite preview",
15
+ "test": "tsc && prettier --check . && eslint . && stylelint \"**/*.css\"",
16
+ "start": "node --experimental-specifier-resolution=node dist/server.js",
17
+ "docker": "npm run docker:build && npm run docker:run",
18
+ "docker:build": "docker build -t auscultate .",
19
+ "docker:run": "docker run -it -p 7860:7860 auscultate",
20
+ "lint": "eslint **/*.{js,jsx,ts,tsx} --fix --cache",
21
+ "format": "prettier **/*.{js,jsx,ts,tsx,css,html,json,md,mdx} --write"
22
+ },
23
+ "devDependencies": {
24
+ "@babel/core": "^7.22.10",
25
+ "@babel/node": "^7.22.10",
26
+ "@babel/preset-env": "^7.22.10",
27
+ "@types/cors": "^2.8.13",
28
+ "@types/express": "^4.17.17",
29
+ "@types/node": "^20.4.9",
30
+ "@types/react": "^18.2.20",
31
+ "@types/react-dom": "^18.2.7",
32
+ "@types/react-helmet": "^6.1.6",
33
+ "@types/react-router-dom": "^5.3.3",
34
+ "@typescript-eslint/eslint-plugin": "^6.3.0",
35
+ "@typescript-eslint/parser": "^6.3.0",
36
+ "@vitejs/plugin-react-refresh": "^1.3.6",
37
+ "autoprefixer": "^10.4.14",
38
+ "concurrently": "^8.2.0",
39
+ "daisyui": "^3.5.1",
40
+ "eslint": "^8.46.0",
41
+ "eslint-config-prettier": "^9.0.0",
42
+ "http-proxy-middleware": "^2.0.6",
43
+ "nodemon": "^3.0.1",
44
+ "postcss": "^8.4.27",
45
+ "prettier": "3.0.1",
46
+ "tailwindcss": "^3.3.3",
47
+ "ts-node": "^10.9.1",
48
+ "typescript": "^5.1.6",
49
+ "vite": "^4.4.9"
50
+ },
51
+ "dependencies": {
52
+ "@hapi/boom": "^10.0.1",
53
+ "@vanillaes/csv": "^3.0.1",
54
+ "axios": "^1.4.0",
55
+ "cors": "^2.8.5",
56
+ "dotenv": "^16.3.1",
57
+ "express": "^4.18.2",
58
+ "react": "^18.2.0",
59
+ "react-dom": "^18.2.0",
60
+ "react-helmet": "^6.1.0",
61
+ "react-router-dom": "^6.15.0",
62
+ "wavesurfer.js": "^7.1.2",
63
+ "zod": "^3.21.4"
64
+ }
65
+ }
postcss.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ };
src/app/App.tsx ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { Helmet } from 'react-helmet';
3
+ import { useNavigate } from 'react-router-dom';
4
+
5
+ const links = [
6
+ {
7
+ icon: (
8
+ <svg width="64px" height="64px" viewBox="0 0 24 24" fill="none">
9
+ <path
10
+ opacity="0.5"
11
+ d="M2.34594 11.2501C2.12458 10.5866 2 9.92019 2 9.26044C2 3.3495 7.50016 0.662637 12 5.49877C16.4998 0.662637 22 3.34931 22 9.2604C22 9.92017 21.8754 10.5866 21.6541 11.2501H18.6361L18.5241 11.25C17.9784 11.2491 17.4937 11.2483 17.0527 11.4447C16.6116 11.6411 16.2879 12.002 15.9233 12.4084L15.8485 12.4918L14.8192 13.6354C14.7164 13.7496 14.5379 13.7463 14.4401 13.6277L10.8889 9.32318C10.7493 9.15391 10.6 8.97281 10.454 8.8384C10.2839 8.68188 10.0325 8.50581 9.68096 8.4847C9.32945 8.46359 9.05875 8.60829 8.87115 8.74333C8.71006 8.8593 8.54016 9.02123 8.38136 9.17258L6.85172 10.6294C6.37995 11.0787 6.28151 11.1553 6.17854 11.1964C6.07557 11.2376 5.9515 11.2501 5.3 11.2501H2.34594Z"
12
+ fill="#1C274C"
13
+ />
14
+ <path
15
+ d="M21.6538 11.2499H18.6359L18.5239 11.2497C17.9781 11.2489 17.4935 11.2481 17.0524 11.4445C16.6114 11.6409 16.2876 12.0018 15.9231 12.4082L15.8482 12.4915L14.819 13.6352C14.7162 13.7494 14.5377 13.7461 14.4399 13.6275L10.8886 9.32297C10.7491 9.1537 10.5998 8.9726 10.4537 8.83819C10.2837 8.68166 10.0322 8.5056 9.68072 8.48449C9.32922 8.46337 9.05852 8.60808 8.87092 8.74312C8.70983 8.85908 8.53993 9.02101 8.38113 9.17236L6.85149 10.6292C6.37972 11.0785 6.28128 11.155 6.17831 11.1962C6.07534 11.2374 5.95126 11.2499 5.29977 11.2499H2.3457C3.38166 14.3548 6.5372 17.3936 8.9615 19.3705C10.2935 20.4567 10.9595 20.9998 11.9998 20.9998C13.0401 20.9998 13.7061 20.4567 15.038 19.3705C17.4623 17.3936 20.6179 14.3548 21.6538 11.2499Z"
16
+ fill="#1C274C"
17
+ />
18
+ </svg>
19
+ ),
20
+ title: 'Auscultation',
21
+ description: 'Heart and breath sounds',
22
+ link: 'https://lysine-auscultate.hf.space/',
23
+ },
24
+ ];
25
+
26
+ export default function App() {
27
+ const navigate = useNavigate();
28
+ return (
29
+ <div className="flex flex-col gap-8 w-full items-center p-4 pt-16">
30
+ <Helmet>
31
+ <title>Clinical Database</title>
32
+ </Helmet>
33
+ <p className="text-3xl text-center">Clinical Database</p>
34
+ <p>A series of websites presenting med-related data for practice.</p>
35
+ {links.map(link => (
36
+ <div className="card lg:card-side bg-base-300 shadow-xl lg:min-w-[500px]">
37
+ <figure className="bg-base-content p-4">{link.icon}</figure>
38
+ <div className="card-body">
39
+ <h2 className="card-title">{link.title}</h2>
40
+ <p>{link.description}</p>
41
+ <div className="card-actions justify-end mt-4">
42
+ <button
43
+ className="btn btn-primary"
44
+ onClick={() => navigate(link.link)}
45
+ >
46
+ Enter
47
+ </button>
48
+ </div>
49
+ </div>
50
+ </div>
51
+ ))}
52
+ </div>
53
+ );
54
+ }
src/app/favicon.svg ADDED
src/app/globals.css ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ body {
6
+ margin: 0;
7
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
8
+ Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
9
+ -webkit-font-smoothing: antialiased;
10
+ -moz-osx-font-smoothing: grayscale;
11
+ }
12
+
13
+ code {
14
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
15
+ monospace;
16
+ }
src/app/main.tsx ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import './globals.css';
4
+ import App from './App';
5
+ import { createBrowserRouter, RouterProvider } from 'react-router-dom';
6
+
7
+ const router = createBrowserRouter([
8
+ {
9
+ path: '/',
10
+ element: <App />,
11
+ },
12
+ ]);
13
+
14
+ const container = document.querySelector('#root');
15
+ const root = createRoot(container!);
16
+ root.render(
17
+ <React.StrictMode>
18
+ <RouterProvider router={router} />
19
+ </React.StrictMode>
20
+ );
src/lib/helper.ts ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Request, Response, NextFunction } from 'express';
2
+ import { AnyZodObject, z } from 'zod';
3
+ import { badRequest } from '@hapi/boom';
4
+
5
+ function indent(str: string, spaces: number) {
6
+ return str
7
+ .split('\n')
8
+ .map(line => ' '.repeat(spaces) + line)
9
+ .join('\n');
10
+ }
11
+
12
+ function extractZodMessage(error: any): string {
13
+ if (Array.isArray(error)) {
14
+ return error.map(extractZodMessage).join('\n');
15
+ } else {
16
+ let union: string[] = [];
17
+ if ('unionErrors' in error) {
18
+ union = error.unionErrors.map(extractZodMessage);
19
+ } else if ('issues' in error) {
20
+ union = error.issues.map(extractZodMessage);
21
+ }
22
+ if (
23
+ 'message' in error &&
24
+ typeof error.message === 'string' &&
25
+ !error.message.includes('\n')
26
+ ) {
27
+ if (union.length === 0) return error.message;
28
+ return error.message + '\n' + indent(union.join('\n'), 2);
29
+ } else if (union.length > 0) {
30
+ return union.join('\n');
31
+ } else {
32
+ return '';
33
+ }
34
+ }
35
+ }
36
+
37
+ export async function validate<T extends AnyZodObject>(
38
+ req: Request,
39
+ schema: T
40
+ ): Promise<z.infer<T>> {
41
+ try {
42
+ return await schema.parseAsync(req);
43
+ } catch (error: any) {
44
+ throw badRequest(extractZodMessage(error));
45
+ }
46
+ }
47
+
48
+ export function wrap(
49
+ fn: (req: Request, res: Response, next: NextFunction) => Promise<any>
50
+ ) {
51
+ return async function (req: Request, res: Response, next: NextFunction) {
52
+ try {
53
+ return await fn(req, res, next);
54
+ } catch (err) {
55
+ next(err);
56
+ }
57
+ };
58
+ }
src/server.ts ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import 'dotenv/config';
2
+ import express, { Request, Response, NextFunction } from 'express';
3
+ import cors from 'cors';
4
+ import { isBoom } from '@hapi/boom';
5
+ import { fileURLToPath } from 'url';
6
+ import { dirname, join } from 'path';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+
11
+ const { PORT = 7860 } = process.env;
12
+
13
+ const app = express();
14
+
15
+ // Enable cross-origin resource sharing
16
+ app.use(cors());
17
+
18
+ // Middleware that parses json and looks at requests where the Content-Type header matches the type option.
19
+ app.use(express.json());
20
+
21
+ // Serve app production bundle
22
+ app.use(express.static('dist/app'));
23
+
24
+ app.use((err: unknown, _req: Request, res: Response, next: NextFunction) => {
25
+ if (res.headersSent) {
26
+ return next(err);
27
+ }
28
+ if (isBoom(err)) {
29
+ return res.status(err.output.statusCode).json(err.output.payload);
30
+ }
31
+ next(err);
32
+ });
33
+
34
+ // Handle client routing, return all requests to the app
35
+ app.get('*', (_req, res) => {
36
+ res.sendFile(join(__dirname, 'app/index.html'));
37
+ });
38
+
39
+ app.listen(PORT, () => {
40
+ console.log(`Server listening at http://localhost:${PORT}`);
41
+ });
tailwind.config.js ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
4
+ theme: {
5
+ extend: {},
6
+ },
7
+ plugins: [require('daisyui')],
8
+ };
tsconfig.json ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
6
+ "moduleResolution": "Node",
7
+ "strict": true,
8
+ "sourceMap": true,
9
+ "resolveJsonModule": true,
10
+ "esModuleInterop": true,
11
+ "types": ["vite/client", "node"],
12
+ "noEmit": true,
13
+ "noUnusedLocals": true,
14
+ "noUnusedParameters": true,
15
+ "noImplicitReturns": true,
16
+ "jsx": "react",
17
+ "allowJs": false,
18
+ "isolatedModules": true,
19
+ "skipLibCheck": false,
20
+ "allowSyntheticDefaultImports": true,
21
+ "forceConsistentCasingInFileNames": true
22
+ },
23
+ "include": ["./src/app"]
24
+ }
tsconfig.server.json ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "module": "ES2022",
6
+ "target": "ES2022",
7
+ "noEmit": false
8
+ },
9
+ "include": ["src/*.ts"],
10
+ "exclude": ["src/app"]
11
+ }
vite.config.js ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import dotenv from 'dotenv';
2
+ dotenv.config();
3
+
4
+ import { defineConfig } from 'vite';
5
+ import reactRefresh from '@vitejs/plugin-react-refresh';
6
+
7
+ const { PORT = 7860 } = process.env;
8
+
9
+ // https://vitejs.dev/config/
10
+ export default defineConfig({
11
+ plugins: [reactRefresh()],
12
+ server: {
13
+ proxy: {
14
+ '/api': {
15
+ target: `http://localhost:${PORT}`,
16
+ changeOrigin: true,
17
+ },
18
+ },
19
+ },
20
+ build: {
21
+ outDir: 'dist/app',
22
+ },
23
+ });