├── frontend
├── src
│ ├── App.css
│ ├── vite-env.d.ts
│ ├── index.css
│ ├── models
│ │ ├── Author.ts
│ │ └── Books.ts
│ ├── ErrorPage.tsx
│ ├── components
│ │ ├── IconView.tsx
│ │ ├── IconDelete.tsx
│ │ ├── IconEdit.tsx
│ │ ├── DeleteBookModal.tsx
│ │ ├── DeleteAuthorModal.tsx
│ │ ├── AuthorCreateEditForm.tsx
│ │ ├── ViewAuthorModal.tsx
│ │ ├── AddEditAuthorModal.tsx
│ │ ├── BookCreateEditForm.tsx
│ │ ├── ViewBookModal.tsx
│ │ └── AddEditBookModal.tsx
│ ├── main.tsx
│ ├── assets
│ │ └── react.svg
│ ├── App.tsx
│ ├── AuthorsPage.tsx
│ └── BooksPage.tsx
├── postcss.config.js
├── public
│ ├── ss
│ │ ├── books.png
│ │ ├── authors.png
│ │ └── dashboard.png
│ └── vite.svg
├── tailwind.config.js
├── vite.config.ts
├── tsconfig.node.json
├── .gitignore
├── index.html
├── .eslintrc.cjs
├── tsconfig.json
├── package.json
├── README.md
└── db.json
├── backend
├── .gitignore
├── server.js
├── configs
│ └── db.js
├── package.json
├── app.js
├── routes
│ └── index.js
├── controllers
│ ├── AuthorsController.js
│ └── BooksController.js
├── db.sql
└── package-lock.json
└── README.md
/frontend/src/App.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/frontend/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
--------------------------------------------------------------------------------
/backend/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .vscode/
3 | node_modules/
4 | build/
5 | tmp/
6 | temp/
7 | .DS_Store
8 | dist/*
--------------------------------------------------------------------------------
/frontend/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/frontend/public/ss/books.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Learn-It-Right-Way/lirw-react-node-mysql-app/HEAD/frontend/public/ss/books.png
--------------------------------------------------------------------------------
/frontend/public/ss/authors.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Learn-It-Right-Way/lirw-react-node-mysql-app/HEAD/frontend/public/ss/authors.png
--------------------------------------------------------------------------------
/frontend/public/ss/dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Learn-It-Right-Way/lirw-react-node-mysql-app/HEAD/frontend/public/ss/dashboard.png
--------------------------------------------------------------------------------
/frontend/src/models/Author.ts:
--------------------------------------------------------------------------------
1 | export interface Author {
2 | id: number;
3 | name: string;
4 | birthday: string;
5 | bio: string;
6 | createdAt: string;
7 | updatedAt: string;
8 | }
--------------------------------------------------------------------------------
/backend/server.js:
--------------------------------------------------------------------------------
1 | const app = require('./app');
2 | const port = process.env.PORT || 3200;
3 |
4 | app.listen(port, () => {
5 | console.log(`Server is running on port http://localhost:${port}`);
6 | });
--------------------------------------------------------------------------------
/frontend/tailwind.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | content: [
3 | "./index.html",
4 | "./src/**/*.{js,ts,jsx,tsx}",
5 | ],
6 | theme: {
7 | extend: {},
8 | },
9 | plugins: [],
10 | }
--------------------------------------------------------------------------------
/frontend/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react-swc'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/backend/configs/db.js:
--------------------------------------------------------------------------------
1 | const mysql = require('mysql2');
2 |
3 | const db = mysql.createConnection({
4 | host: 'localhost',
5 | port: '3306',
6 | user: 'root',
7 | password: '12345678',
8 | database: 'react_node_app'
9 | });
10 |
11 | module.exports = db;
--------------------------------------------------------------------------------
/frontend/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true,
8 | "strict": true
9 | },
10 | "include": ["vite.config.ts"]
11 | }
12 |
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 | .env
--------------------------------------------------------------------------------
/frontend/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/frontend/src/ErrorPage.tsx:
--------------------------------------------------------------------------------
1 | import { useRouteError } from "react-router-dom";
2 |
3 | export default function ErrorPage() {
4 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
5 | const error: any = useRouteError();
6 |
7 | return (
8 |
9 |
Oops!
10 |
Sorry, an unexpected error has occurred.
11 |
12 | {error.statusText || error.message}
13 |
14 |
15 | );
16 | }
--------------------------------------------------------------------------------
/frontend/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:@typescript-eslint/recommended',
7 | 'plugin:react-hooks/recommended',
8 | ],
9 | ignorePatterns: ['dist', '.eslintrc.cjs'],
10 | parser: '@typescript-eslint/parser',
11 | plugins: ['react-refresh'],
12 | rules: {
13 | 'react-refresh/only-export-components': [
14 | 'warn',
15 | { allowConstantExport: true },
16 | ],
17 | },
18 | }
19 |
--------------------------------------------------------------------------------
/backend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mysql-express",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "node server.js",
8 | "test": "echo \"Error: no test specified\" && exit 1",
9 | "serve": "pm2 start server.js --interpreter node"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "body-parser": "^1.20.2",
16 | "cors": "^2.8.5",
17 | "express": "^4.19.2",
18 | "mysql2": "^3.10.0"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/frontend/src/components/IconView.tsx:
--------------------------------------------------------------------------------
1 | export const IconView = () => {
2 | return (
3 |
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/frontend/src/components/IconDelete.tsx:
--------------------------------------------------------------------------------
1 | export const IconDelete = () => {
2 | return (
3 |
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/backend/app.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const mysql = require('mysql2');
3 | const bodyParser = require('body-parser');
4 | const routes = require('./routes');
5 | const cors = require('cors');
6 | const db = require('./configs/db'); // Import the db connection
7 |
8 | const app = express();
9 |
10 | app.use(cors());
11 | app.use(bodyParser.json());
12 |
13 | db.connect((err) => {
14 | if (err) {
15 | console.error('Error connecting to MySQL: ' + err.stack);
16 | return;
17 | }
18 | console.log('Connected to MySQL Database');
19 | });
20 |
21 | // Add your routes here
22 | app.use('/api', routes);
23 |
24 | module.exports = app;
--------------------------------------------------------------------------------
/frontend/src/components/IconEdit.tsx:
--------------------------------------------------------------------------------
1 | export const IconEdit = () => {
2 | return (
3 |
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/backend/routes/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const booksController = require('../controllers/BooksController');
3 | const authorsController = require('../controllers/AuthorsController');
4 |
5 | const router = express.Router();
6 |
7 | router.get('/authors', authorsController.get);
8 | router.post('/authors', authorsController.create);
9 | router.put('/authors/:id', authorsController.update);
10 | router.delete('/authors/:id', authorsController.delete);
11 |
12 | router.get('/books', booksController.get);
13 | router.post('/books', booksController.create);
14 | router.put('/books/:id', booksController.update);
15 | router.delete('/books/:id', booksController.delete);
16 |
17 | module.exports = router;
--------------------------------------------------------------------------------
/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true
22 | },
23 | "include": ["src"],
24 | "references": [{ "path": "./tsconfig.node.json" }]
25 | }
26 |
--------------------------------------------------------------------------------
/frontend/src/models/Books.ts:
--------------------------------------------------------------------------------
1 | export interface Book {
2 | id: number;
3 | title: string;
4 | releaseDate: string;
5 | description: string;
6 | pages: number;
7 | createdAt: string;
8 | updatedAt: string;
9 | authorId: number;
10 | name: string;
11 | birthday: string;
12 | bio: string;
13 | }
14 |
15 | export interface BookDTO {
16 | id: number;
17 | title: string;
18 | releaseDate: string;
19 | description: string;
20 | pages: number;
21 | createdAt: string;
22 | updatedAt: string;
23 | author: string
24 | }
25 |
26 | export interface BookFormDTO {
27 | id: number;
28 | title: string;
29 | releaseDate: string;
30 | description: string;
31 | pages: number;
32 | createdAt: string;
33 | updatedAt: string;
34 | author?: number
35 | }
--------------------------------------------------------------------------------
/frontend/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import {
4 | createBrowserRouter,
5 | RouterProvider,
6 | } from "react-router-dom";
7 | import App from './App.tsx'
8 | import './index.css'
9 | import ErrorPage from './ErrorPage.tsx';
10 | import BooksPage from './BooksPage.tsx';
11 | import AuthorsPage from './AuthorsPage.tsx';
12 |
13 | const router = createBrowserRouter([
14 | {
15 | path: "/",
16 | element: ,
17 | errorElement: ,
18 | },
19 | {
20 | path: "/books",
21 | element:
22 | },
23 | {
24 | path: "/authors",
25 | element:
26 | },
27 | ]);
28 |
29 | ReactDOM.createRoot(document.getElementById('root')!).render(
30 |
31 |
32 | ,
33 | )
34 |
--------------------------------------------------------------------------------
/frontend/src/components/DeleteBookModal.tsx:
--------------------------------------------------------------------------------
1 | import { Modal } from "antd";
2 | import { Book } from "../models/Books";
3 |
4 | type Props = {
5 | isModalOpen: boolean;
6 | book?: Book;
7 | setIsModalOpen: React.Dispatch>;
8 | onOk: () => void
9 | }
10 |
11 | export const DeleteBookModal = ({ isModalOpen, book, setIsModalOpen, onOk }: Props) => {
12 | const handleOk = () => {
13 | onOk();
14 | setIsModalOpen(false);
15 | }
16 |
17 | const handleCancel = () => {
18 | setIsModalOpen(false);
19 | }
20 |
21 | return (
22 |
23 | Are you sure, you want to delete book {` ${book?.title}`}?
24 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/frontend/src/components/DeleteAuthorModal.tsx:
--------------------------------------------------------------------------------
1 | import { Modal } from "antd";
2 | import { Author } from "../models/Author";
3 |
4 | type Props = {
5 | isModalOpen: boolean;
6 | author?: Author;
7 | setIsModalOpen: React.Dispatch>;
8 | onOk: () => void
9 | }
10 |
11 | export const DeleteAuthorModal = ({ isModalOpen, author, setIsModalOpen, onOk }: Props) => {
12 | const handleOk = () => {
13 | onOk();
14 | setIsModalOpen(false);
15 | }
16 |
17 | const handleCancel = () => {
18 | setIsModalOpen(false);
19 | }
20 |
21 | return (
22 |
23 | Are you sure, you want to delete author {` ${author?.name}`}?
24 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/frontend/src/components/AuthorCreateEditForm.tsx:
--------------------------------------------------------------------------------
1 | import { Form, FormInstance, Input } from "antd"
2 | import { useEffect } from "react";
3 | import { Author } from "../models/Author";
4 |
5 | interface Props {
6 | initialValues?: Author;
7 | onFormInstanceReady: (instance: FormInstance) => void;
8 | }
9 |
10 | const { TextArea } = Input;
11 |
12 | export const AuthorCreateEditForm = ({ initialValues, onFormInstanceReady }: Props) => {
13 | const [form] = Form.useForm();
14 |
15 | useEffect(() => {
16 | onFormInstanceReady(form);
17 | }, []);
18 |
19 | return (
20 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview",
11 | "server": "json-server db.json"
12 | },
13 | "dependencies": {
14 | "antd": "^5.17.3",
15 | "chart.js": "^4.4.3",
16 | "react": "^18.2.0",
17 | "react-chartjs-2": "^5.2.0",
18 | "react-dom": "^18.2.0",
19 | "react-router-dom": "^6.23.1"
20 | },
21 | "devDependencies": {
22 | "@types/react": "^18.2.66",
23 | "@types/react-dom": "^18.2.22",
24 | "@typescript-eslint/eslint-plugin": "^7.2.0",
25 | "@typescript-eslint/parser": "^7.2.0",
26 | "@vitejs/plugin-react-swc": "^3.5.0",
27 | "autoprefixer": "^10.4.19",
28 | "eslint": "^8.57.0",
29 | "eslint-plugin-react-hooks": "^4.6.0",
30 | "eslint-plugin-react-refresh": "^0.4.6",
31 | "json-server": "^1.0.0-beta.0",
32 | "postcss": "^8.4.38",
33 | "tailwindcss": "^3.4.3",
34 | "typescript": "^5.2.2",
35 | "vite": "^5.2.0"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | # React + TypeScript + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
10 | ## Expanding the ESLint configuration
11 |
12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
13 |
14 | - Configure the top-level `parserOptions` property like this:
15 |
16 | ```js
17 | export default {
18 | // other rules...
19 | parserOptions: {
20 | ecmaVersion: 'latest',
21 | sourceType: 'module',
22 | project: ['./tsconfig.json', './tsconfig.node.json'],
23 | tsconfigRootDir: __dirname,
24 | },
25 | }
26 | ```
27 |
28 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
29 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
31 |
--------------------------------------------------------------------------------
/frontend/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/components/ViewAuthorModal.tsx:
--------------------------------------------------------------------------------
1 | import { Modal } from "antd";
2 | import { Author } from "../models/Author";
3 |
4 | type Props = {
5 | isModalOpen: boolean;
6 | author?: Author;
7 | setIsModalOpen: React.Dispatch>;
8 | }
9 |
10 | enum ValueMappings {
11 | "id" = "ID",
12 | "name" = "NAME",
13 | "birthday" = "BIRTHDAY",
14 | "bio" = "DESCRIPTION",
15 | "createdAt" = "CREATED DATE",
16 | "updatedAt" = "UPDATED DATE",
17 | }
18 |
19 | export const ViewAuthorModal = ({ isModalOpen, author, setIsModalOpen }: Props) => {
20 | const handleCancel = () => {
21 | setIsModalOpen(false);
22 | }
23 |
24 | const renderAuthorDetails = () => {
25 | if (author) {
26 | const details = Object.keys(author).map((key: string) => {
27 | return (
28 |
29 |
{ValueMappings[key as keyof typeof ValueMappings]}
30 |
{author[key as keyof Author]}
31 |
32 | )
33 | });
34 |
35 | return details;
36 | }
37 |
38 | return null;
39 | }
40 |
41 | return (
42 |
52 |
53 | {renderAuthorDetails()}
54 |
55 |
56 | )
57 | }
58 |
--------------------------------------------------------------------------------
/frontend/src/components/AddEditAuthorModal.tsx:
--------------------------------------------------------------------------------
1 | import { Modal } from "antd";
2 | import { FormInstance } from "antd/es/form/Form";
3 | import { useState } from "react";
4 | import { AuthorCreateEditForm } from "./AuthorCreateEditForm";
5 | import { Author } from "../models/Author";
6 |
7 | type Props = {
8 | isModalOpen: boolean;
9 | isEdit: boolean;
10 | initialValues?: Author;
11 | setIsModalOpen: React.Dispatch>;
12 | onOk: (values: Author) => void;
13 | }
14 |
15 | export const AddEditAuthorModal = ({
16 | isModalOpen, isEdit, initialValues, setIsModalOpen, onOk
17 | }: Props) => {
18 | const [formInstance, setFormInstance] = useState();
19 |
20 | const handleOk = async () => {
21 | try {
22 | const values = await formInstance?.validateFields();
23 | formInstance?.resetFields();
24 | onOk(values);
25 | setIsModalOpen(false);
26 | } catch (error) {
27 | console.log('Failed:', error);
28 | }
29 | }
30 |
31 | const handleCancel = () => {
32 | setIsModalOpen(false);
33 | }
34 |
35 | return (
36 |
46 | {
49 | setFormInstance(instance);
50 | }}
51 | />
52 |
53 | )
54 | }
55 |
--------------------------------------------------------------------------------
/frontend/src/components/BookCreateEditForm.tsx:
--------------------------------------------------------------------------------
1 | import { Form, FormInstance, Input, Select } from "antd"
2 | import { useEffect } from "react";
3 | import { BookFormDTO } from "../models/Books";
4 | import { Author } from "../models/Author";
5 |
6 | interface Props {
7 | initialValues?: BookFormDTO;
8 | authors: Author[];
9 | onFormInstanceReady: (instance: FormInstance) => void;
10 | }
11 |
12 | const { TextArea } = Input;
13 |
14 | export const BookCreateEditForm = ({ initialValues, authors, onFormInstanceReady }: Props) => {
15 | const [form] = Form.useForm();
16 |
17 | useEffect(() => {
18 | onFormInstanceReady(form);
19 | }, []);
20 |
21 | const renderOptions = () => {
22 | return authors.map(author => ({ value: author.id, label: author.name }))
23 | }
24 |
25 | return (
26 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
44 |
45 |
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/frontend/src/components/ViewBookModal.tsx:
--------------------------------------------------------------------------------
1 | import { Modal } from "antd";
2 | import { BookDTO } from "../models/Books";
3 |
4 | type Props = {
5 | isModalOpen: boolean;
6 | book?: BookDTO;
7 | setIsModalOpen: React.Dispatch>;
8 | }
9 |
10 | enum ValueMappings {
11 | "id" = "ID",
12 | "title" = "TITLE",
13 | "releaseDate" = "RELEASE DATE",
14 | "description" = "BOOK DESCRIPTION",
15 | "pages" = "TOTAL PAGES",
16 | "author" = "AUTHOR",
17 | "birthday" = "BIRTHDAY",
18 | "bio" = "AUTHOR DESCRIPTION",
19 | "createdAt" = "CREATED DATE",
20 | "updatedAt" = "UPDATED DATE",
21 | }
22 |
23 | export const ViewBookModal = ({ isModalOpen, book, setIsModalOpen }: Props) => {
24 | const handleCancel = () => {
25 | setIsModalOpen(false);
26 | }
27 |
28 | const renderBookDetails = () => {
29 | if (book) {
30 | const details = Object.keys(book).map((key: string) => {
31 | return (
32 |
33 |
{ValueMappings[key as keyof typeof ValueMappings]}
34 |
{book[key as keyof BookDTO]}
35 |
36 | )
37 | });
38 |
39 | return details;
40 | }
41 |
42 | return null;
43 | }
44 |
45 | return (
46 |
56 |
57 | {renderBookDetails()}
58 |
59 |
60 | )
61 | }
62 |
--------------------------------------------------------------------------------
/frontend/src/components/AddEditBookModal.tsx:
--------------------------------------------------------------------------------
1 | import { Modal } from "antd";
2 | import { FormInstance } from "antd/es/form/Form";
3 | import { useState } from "react";
4 | import { BookCreateEditForm } from "./BookCreateEditForm";
5 | import { BookFormDTO } from "../models/Books";
6 | import { Author } from "../models/Author";
7 |
8 | type Props = {
9 | isModalOpen: boolean;
10 | isEdit: boolean;
11 | initialValues?: BookFormDTO;
12 | authors: Author[];
13 | setIsModalOpen: React.Dispatch>;
14 | onOk: (values: BookFormDTO) => void;
15 | }
16 |
17 | export const AddEditBookModal = ({
18 | isModalOpen, isEdit, initialValues, authors, setIsModalOpen, onOk
19 | }: Props) => {
20 | const [formInstance, setFormInstance] = useState();
21 |
22 | const handleOk = async () => {
23 | try {
24 | const values = await formInstance?.validateFields();
25 | formInstance?.resetFields();
26 | onOk(values);
27 | setIsModalOpen(false);
28 | } catch (error) {
29 | console.log('Failed:', error);
30 | }
31 | }
32 |
33 | const handleCancel = () => {
34 | setIsModalOpen(false);
35 | }
36 |
37 | return (
38 |
48 | {
52 | setFormInstance(instance);
53 | }}
54 | />
55 |
56 | )
57 | }
58 |
--------------------------------------------------------------------------------
/frontend/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/controllers/AuthorsController.js:
--------------------------------------------------------------------------------
1 | const db = require('../configs/db');
2 |
3 | function AuthorsController() { }
4 |
5 | const getQuery = 'SELECT * FROM author';
6 |
7 | AuthorsController.prototype.get = async (req, res) => {
8 | try {
9 | db.query(getQuery, (err, authors) => {
10 | if (err) {
11 | throw new Error("Error executing query.");
12 | }
13 |
14 | res.status(200).json({
15 | authors: authors.map(author => ({
16 | ...author,
17 | birthday: new Date(author.birthday).toLocaleDateString("en-CA"),
18 | createdAt: new Date(author.createdAt).toLocaleDateString("en-CA"),
19 | updatedAt: new Date(author.updatedAt).toLocaleDateString("en-CA"),
20 | })),
21 | });
22 | });
23 | } catch (error) {
24 | console.error(error);
25 | res.status(500).json({
26 | message:
27 | "Something unexpected has happened. Please try again later.",
28 | });
29 | }
30 | };
31 |
32 | AuthorsController.prototype.create = async (req, res) => {
33 | try {
34 | const { name, birthday, bio } = req.body;
35 |
36 | db.query('INSERT INTO author (name, birthday, bio, createdAt, updatedAt) VALUES (?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)', [
37 | name, new Date(birthday), bio], (err) => {
38 | if (err) {
39 | console.log(err);
40 | throw new Error("Error executing query.");
41 | }
42 |
43 | db.query(getQuery, (err, authors) => {
44 | if (err) {
45 | throw new Error("Error executing query.");
46 | }
47 |
48 | return res.status(200).json({
49 | message: `Author created successfully!`,
50 | authors: authors,
51 | });
52 | });
53 | });
54 | } catch (error) {
55 | console.error(error);
56 | res.status(500).json({
57 | message:
58 | "Something unexpected has happened. Please try again later.",
59 | });
60 | }
61 | };
62 |
63 | AuthorsController.prototype.update = async (req, res) => {
64 | try {
65 | const authorId = req.params.id;
66 | const { name, birthday, bio } = req.body;
67 |
68 | db.query(`UPDATE author SET name = ?, birthday = ?, bio = ?, updatedAt = CURRENT_TIMESTAMP WHERE id = ?`, [
69 | name, new Date(birthday), bio, authorId], (err) => {
70 | if (err) {
71 | console.log(err);
72 | throw new Error("Error executing query.");
73 | }
74 |
75 | db.query(getQuery, (err, authors) => {
76 | if (err) {
77 | throw new Error("Error executing query.");
78 | }
79 |
80 | return res.status(200).json({
81 | message: `Author updated successfully!`,
82 | authors: authors,
83 | });
84 | });
85 | });
86 | } catch (error) {
87 | console.error(error);
88 | res.status(500).json({
89 | message:
90 | "Something unexpected has happened. Please try again later.",
91 | });
92 | }
93 | };
94 |
95 | AuthorsController.prototype.delete = async (req, res) => {
96 | try {
97 | const authorId = req.params.id;
98 |
99 | db.query('DELETE FROM author WHERE id = ?', [authorId], (err, result) => {
100 | if (err) {
101 | throw new Error("Error executing query.");
102 | }
103 |
104 | db.query(getQuery, (err, authors) => {
105 | if (err) {
106 | throw new Error("Error executing query.");
107 | }
108 |
109 | return res.status(200).json({
110 | message: `Author deleted successfully!`,
111 | authors: authors,
112 | });
113 | });
114 | });
115 | } catch (error) {
116 | console.error(error);
117 | res.status(500).json({
118 | message:
119 | "Something unexpected has happened. Please try again later.",
120 | });
121 | }
122 | };
123 |
124 | module.exports = new AuthorsController();
--------------------------------------------------------------------------------
/backend/controllers/BooksController.js:
--------------------------------------------------------------------------------
1 | const db = require('../configs/db');
2 |
3 | function BooksController() { }
4 |
5 | const getQuery = `SELECT b.id as id, b.title as title, b.releaseDate as releaseDate, b.description as description, b.pages as pages,
6 | b.createdAt as createdAt, b.updatedAt as updatedAt, a.id as authorId, a.name as name, a.birthday as birthday, a.bio as bio FROM book b INNER JOIN author a on b.authorId = a.id`;
7 |
8 | BooksController.prototype.get = async (req, res) => {
9 | try {
10 | db.query(getQuery, (err, books) => {
11 | if (err) {
12 | throw new Error("Error executing query.");
13 | }
14 |
15 | res.status(200).json({
16 | books: books.map(book => ({
17 | ...book,
18 | releaseDate: new Date(book.releaseDate).toLocaleDateString("en-CA"),
19 | createdAt: new Date(book.createdAt).toLocaleDateString("en-CA"),
20 | updatedAt: new Date(book.updatedAt).toLocaleDateString("en-CA"),
21 | })),
22 | });
23 | });
24 | } catch (error) {
25 | console.error(error);
26 | res.status(500).json({
27 | message:
28 | "Something unexpected has happened. Please try again later.",
29 | });
30 | }
31 | };
32 |
33 | BooksController.prototype.create = async (req, res) => {
34 | try {
35 | const {
36 | title,
37 | description,
38 | releaseDate,
39 | pages,
40 | author: authorId,
41 | } = req.body;
42 |
43 | db.query('INSERT INTO book (title, releaseDate, description, pages, authorId, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?, ?)', [
44 | title, new Date(releaseDate), description, pages, authorId, new Date(), new Date()], (err) => {
45 | if (err) {
46 | throw new Error("Error executing query.", err);
47 | }
48 |
49 | db.query(getQuery, (err, books) => {
50 | if (err) {
51 | throw new Error("Error executing query.");
52 | }
53 |
54 | return res.status(200).json({
55 | message: `Book created successfully!`,
56 | books: books,
57 | });
58 | });
59 | });
60 | } catch (error) {
61 | console.error(error);
62 | res.status(500).json({
63 | message:
64 | "Something unexpected has happened. Please try again later.",
65 | });
66 | }
67 | };
68 |
69 | BooksController.prototype.update = async (req, res) => {
70 | try {
71 | const bookId = req.params.id;
72 | const {
73 | title,
74 | description,
75 | releaseDate,
76 | pages,
77 | author: authorId,
78 | } = req.body;
79 |
80 | db.query('UPDATE book SET title = ?, releaseDate = ?, description = ?, pages = ?, authorId = ?, updatedAt = CURRENT_TIMESTAMP WHERE id = ?', [
81 | title, new Date(releaseDate), description, pages, authorId, bookId], (err) => {
82 | if (err) {
83 | console.log(err);
84 | throw new Error("Error executing query.");
85 | }
86 |
87 | db.query(getQuery, (err, books) => {
88 | if (err) {
89 | throw new Error("Error executing query.");
90 | }
91 |
92 | return res.status(200).json({
93 | message: `Book updated successfully!`,
94 | books: books,
95 | });
96 | });
97 | });
98 | } catch (error) {
99 | console.error(error);
100 | res.status(500).json({
101 | message:
102 | "Something unexpected has happened. Please try again later.",
103 | });
104 | }
105 | };
106 |
107 | BooksController.prototype.delete = async (req, res) => {
108 | try {
109 | const bookId = req.params.id;
110 |
111 | db.query('DELETE FROM book WHERE id = ?', [bookId], (err) => {
112 | if (err) {
113 | throw new Error("Error executing query.");
114 | }
115 |
116 | db.query(getQuery, (err, books) => {
117 | if (err) {
118 | throw new Error("Error executing query.");
119 | }
120 |
121 | return res.status(200).json({
122 | message: `Book deleted successfully!`,
123 | books: books,
124 | });
125 | });
126 | });
127 | } catch (error) {
128 | console.error(error);
129 | res.status(500).json({
130 | message:
131 | "Something unexpected has happened. Please try again later.",
132 | });
133 | }
134 | };
135 |
136 | module.exports = new BooksController();
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Learn It Right Way
2 | This project is a full-stack web application built using React js for the frontend, Express js for the backend, and MySQL as the database. The application is designed to demonstrate the implementation of a 3-tier architecture, where the presentation layer (React js), application logic layer (Express js), and data layer (MySQL) are separated into distinct tiers.
3 |
4 |
5 | ## User Interface Screenshots
6 | #### Dashboard
7 | 
8 |
9 | #### Books
10 | 
11 |
12 | #### Authors
13 | 
14 |
15 |
16 | ## Connecting to private EC2 instance via a bastion host
17 | 1. To change the ssh key permission:
18 |
19 | ```bash
20 | chmod 400 your_key.pem
21 | ```
22 |
23 | 2. To start ssh agent:
24 |
25 | ```bash
26 | eval "$(ssh-agent -s)"
27 | ```
28 |
29 | 3. To add key to ssh agent:
30 |
31 | ```bash
32 | ssh-add your_key.pem
33 | ```
34 |
35 | 4. To ssh into bastion host with agent forwarding:
36 |
37 | ```bash
38 | ssh -A ec2-user@bastion_host_public_ip
39 | ```
40 |
41 | 5. To connect private instance from the bastion host:
42 |
43 | ```bash
44 | ssh ec2-user@private_instance_private_ip
45 | ```
46 |
47 | ## Setting up the Data Tier
48 | #### Install MySQL
49 | 1. To download MySQL repository package:
50 |
51 | ```bash
52 | wget https://dev.mysql.com/get/mysql80-community-release-el9-1.noarch.rpm
53 | ```
54 |
55 | 2. To verify the package download:
56 |
57 | ```bash
58 | ls -lrt
59 | ```
60 |
61 | 3. To install MySQL repository package:
62 |
63 | ```bash
64 | sudo dnf install -y mysql80-community-release-el9-1.noarch.rpm
65 | ```
66 |
67 | 4. To import GPG key:
68 |
69 | ```bash
70 | sudo rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2023
71 | ```
72 |
73 | 5. To update package index:
74 |
75 | ```bash
76 | sudo dnf update –y
77 | ```
78 |
79 | 6. To install MySQL server:
80 |
81 | ```bash
82 | sudo dnf install -y mysql-community-server
83 | ```
84 |
85 | 7. To start the mysql service:
86 |
87 | ```bash
88 | sudo systemctl start mysqld
89 | ```
90 |
91 | 8. To enable mysql to start on boot:
92 |
93 | ```bash
94 | sudo systemctl enable mysqld
95 | ```
96 |
97 | 9. To secure the mysql installation:
98 |
99 | ```bash
100 | sudo grep 'temporary password' /var/log/mysqld.log
101 |
102 | sudo mysql_secure_installation
103 | ```
104 |
105 | 10. To create database and restore data, please refer SQL scripts on [db.sql](./backend/db.sql) file.
106 |
107 |
108 | ## Setting up the Application Tier
109 | #### Install GIT
110 | ```bash
111 | sudo yum update -y
112 |
113 | sudo yum install git -y
114 |
115 | git — version
116 | ```
117 |
118 | #### Clone repository
119 | ```bash
120 | git clone https://github.com/learnItRightWay01/react-node-mysql-app.git
121 | ```
122 |
123 | #### Install node.js
124 | 1. To install node version manager (nvm)
125 | ```bash
126 | curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
127 | ```
128 |
129 | 2. To load nvm
130 | ```bash
131 | source ~/.bashrc
132 | ```
133 |
134 | 3. To use nvm to install the latest LTS version of Node.js
135 | ```bash
136 | nvm install --lts
137 | ```
138 |
139 | 4. To test that Node.js is installed and running
140 | ```bash
141 | node -e "console.log('Running Node.js ' + process.version)"
142 | ```
143 |
144 | ## Setting up the Presentation Tier
145 | #### Install GIT
146 | ```
147 | PLEASE REFER ABOVE
148 | ```
149 |
150 | #### Clone repository
151 | ```
152 | PLEASE REFER ABOVE
153 | ```
154 |
155 | #### Install node.js
156 | ```
157 | PLEASE REFER ABOVE
158 | ```
159 |
160 | #### Install NGINX
161 | ```bash
162 | dnf search nginx
163 |
164 | sudo dnf install nginx
165 |
166 | sudo systemctl restart nginx
167 |
168 | nginx -v
169 | ```
170 |
171 | #### Copy react.js build files
172 | ```bash
173 | sudo cp -r dist /usr/share/nginx/html
174 | ```
175 |
176 | #### Update NGINX config
177 | 1. Server name and root
178 | ```
179 | server_name domain.com www.subdomain.com
180 | root /usr/share/nginx/html/dist
181 | ```
182 |
183 | 2. Setup reverse proxy
184 | ```
185 | location /api {
186 | proxy_pass http://application_tier_instance_private_ip:3200/api;
187 | }
188 | ```
189 |
190 | 3. Restart NGINX
191 | ```
192 | sudo systemctl restart nginx
193 | ```
194 |
195 | ## User data scripts
196 | #### Install NGINX
197 | For [AWS solutions - 06](https://youtu.be/snQlL0fJI3Q) and [AWS solutions - 07](https://youtu.be/eRX1FI2cFi8)
198 |
199 | ```bash
200 | #!/bin/bash
201 | # Update package lists
202 | yum update -y
203 |
204 | # Install Nginx
205 | yum install -y nginx
206 |
207 | # Stop and disable default service (optional)
208 | systemctl stop nginx
209 | systemctl disable nginx
210 |
211 | # Create a custom welcome message file
212 | echo "Welcome to Presentation Tier EC2 instance in Availability Zone B." > /usr/share/nginx/html/index.html
213 |
214 | # Start and enable the Nginx service
215 | systemctl start nginx
216 | systemctl enable nginx
217 | ```
218 |
--------------------------------------------------------------------------------
/backend/db.sql:
--------------------------------------------------------------------------------
1 | -- Create Database and User
2 | CREATE DATABASE react_node_app;
3 | CREATE USER 'appuser'@'%' IDENTIFIED BY 'learnIT02#';
4 | GRANT ALL PRIVILEGES ON react_node_app.* TO ' appuser'@'%';
5 | FLUSH PRIVILEGES;
6 |
7 |
8 | -- Create Tables
9 | CREATE TABLE `author` (
10 | `id` int NOT NULL AUTO_INCREMENT,
11 | `name` varchar(255) NOT NULL,
12 | `birthday` date NOT NULL,
13 | `bio` text NOT NULL,
14 | `createdAt` date NOT NULL,
15 | `updatedAt` date NOT NULL,
16 | PRIMARY KEY (`id`)
17 | ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
18 |
19 | CREATE TABLE `book` (
20 | `id` int NOT NULL AUTO_INCREMENT,
21 | `title` varchar(255) NOT NULL,
22 | `releaseDate` date NOT NULL,
23 | `description` text NOT NULL,
24 | `pages` int NOT NULL,
25 | `createdAt` date NOT NULL,
26 | `updatedAt` date NOT NULL,
27 | `authorId` int DEFAULT NULL,
28 | PRIMARY KEY (`id`),
29 | KEY `FK_66a4f0f47943a0d99c16ecf90b2` (`authorId`),
30 | CONSTRAINT `FK_66a4f0f47943a0d99c16ecf90b2` FOREIGN KEY (`authorId`) REFERENCES `author` (`id`)
31 | ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
32 |
33 |
34 | -- Restore Data
35 | INSERT INTO `author` VALUES (1,'J.K. Rowling (Joanne Kathleen Rowling)','1965-07-31','J.K. Rowling is a British author best known for writing the Harry Potter fantasy series. The series has won multiple awards and sold over 500 million copies, becoming the best-selling book series in history. Rowling has also written other novels, including The Casual Vacancy and the Cormoran Strike crime series under the pen name Robert Galbraith.','2024-05-29','2024-05-29'),(3,'Jane Austen','1775-12-16','Jane Austen was an English novelist known for her wit, social commentary, and romantic stories. Her six major novels, which explore themes of love, marriage, and money, have earned her a place as one of the greatest writers in the English language.','2024-05-29','2024-05-29'),(4,'Harper Lee','1960-07-11','Harper Lee was an American novelist best known for her Pulitzer Prize-winning novel To Kill a Mockingbird. The novel explores themes of racial injustice and the importance of compassion. Lee published a sequel, Go Set a Watchman, in 2015.','2024-05-29','2024-05-29'),(5,'J.R.R. Tolkien','1954-07-29','J.R.R. Tolkien was a British philologist and writer best known for his fantasy novels The Hobbit and The Lord of the Rings. Tolkien\'s works have had a profound influence on the fantasy genre and popular culture.','2024-05-29','2024-05-29'),(6,'Mary Shelley','1818-03-03','Mary Shelley was a British novelist, playwright, and short story writer, the daughter of Mary Wollstonecraft Godwin and the wife of poet Percy Bysshe Shelley. Frankenstein, or, The Modern Prometheus (1818) is her most famous work.','2024-05-29','2024-05-29'),(7,'Douglas Adams','1979-10-12','Douglas Adams was an English science fiction writer, satirist, humorist, dramatist, screenwriter, and occasional actor. He is best known for the Hitchhiker\'s Guide to the Galaxy comedy series, which inspired a radio comedy, several books, stage shows, comic books, a 1981 TV series, a 1984 video game, a 2005 feature film, and a 2008 sequel film.','2024-05-29','2024-05-29');
36 |
37 | INSERT INTO `book` VALUES (1,'Harry Potter and the Sorcerer\'s Stone','1997-07-26','On his birthday, Harry Potter discovers that he is the son of two well-known wizards, from whom he has inherited magical powers. He must attend a famous school of magic and sorcery, where he establishes a friendship with two young men who will become his companions on his adventure. During his first year at Hogwarts, he discovers that a malevolent and powerful wizard named Voldemort is in search of a philosopher\'s stone that prolongs the life of its owner.',223,'2024-05-29','2024-05-29',1),(3,'Harry Potter and the chamber of secrets','1998-07-02','Harry Potter and the sophomores investigate a malevolent threat to their Hogwarts classmates, a menacing beast that hides within the castle.',251,'2024-05-29','2024-05-29',1),(4,'Pride and Prejudice','1813-01-28','An English novel of manners by Jane Austen, first published in 1813. The story centres on the relationships among the Bennet sisters, in particular Elizabeth Bennet the middle daughter, and the wealthy Mr. Darcy. Austen satirizes the social classes of the English gentry through a witty and ironic narrative voice.',224,'2024-05-29','2024-05-29',3),(5,'Harry Potter and the Prisoner of Azkaban','1999-07-08','Harry\'s third year of studies at Hogwarts is threatened by Sirius Black\'s escape from Azkaban prison. Apparently, it is a dangerous wizard who was an accomplice of Lord Voldemort and who will try to take revenge on Harry Potter.',317,'2024-05-29','2024-05-29',1),(6,'Harry Potter and the Goblet of Fire','2000-07-08','Hogwarts prepares for the Triwizard Tournament, in which three schools of wizardry will compete. To everyone\'s surprise, Harry Potter is chosen to participate in the competition, in which he must fight dragons, enter the water and face his greatest fears.',636,'2024-05-29','2024-05-29',1),(7,'The Hitchhiker\'s Guide to the Galaxy','1979-10-12','A comic science fiction comedy series created by Douglas Adams. The story follows the comedic misadventures of Arthur Dent, a hapless Englishman, following the destruction of the Earth by the Vogons, a race of unpleasant bureaucratic aliens. Arthur escapes with his friend Ford Prefect, who reveals himself to be an undercover researcher for the titular Hitchhiker\'s Guide to the Galaxy, a galactic encyclopedia containing information about anything and everything.',184,'2024-05-29','2024-05-29',7),(8,'Frankenstein; or, The Modern Prometheus','1818-03-03','A Gothic novel by Mary Shelley that tells the story of Victor Frankenstein, a young scientist who creates a grotesque creature in an unorthodox scientific experiment. Frankenstein is horrified by his creation and abandons it, but the creature seeks revenge. The novel explores themes of scientific responsibility, creation versus destruction, and the nature of good and evil.',211,'2024-05-29','2024-05-29',6),(9,'The Lord of the Rings: The Fellowship of the Ring','1954-07-29','The first book in J.R.R. Tolkien\'s epic fantasy trilogy, The Lord of the Rings. The Fellowship of the Ring follows hobbit Frodo Baggins as he inherits the One Ring, an evil artifact of power created by the Dark Lord Sauron. Frodo embarks on a quest to destroy the Ring in the fires of Mount Doom, accompanied by a fellowship of eight companions.',482,'2024-05-29','2024-05-29',5);
38 |
--------------------------------------------------------------------------------
/frontend/src/App.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-explicit-any */
2 | import { Button } from 'antd'
3 | import './App.css'
4 | import { Link } from 'react-router-dom';
5 | import {
6 | Chart as ChartJS,
7 | CategoryScale,
8 | LinearScale,
9 | BarElement,
10 | Title,
11 | Tooltip,
12 | Legend,
13 | ChartData,
14 | ArcElement
15 | } from 'chart.js';
16 | import { Bar, Pie } from 'react-chartjs-2';
17 | import { useEffect, useState } from 'react';
18 | import { Book } from './models/Books';
19 | // import { Author } from './models/Author';
20 |
21 | const API_URL = import.meta.env.VITE_API_URL;
22 |
23 | ChartJS.register(
24 | ArcElement,
25 | CategoryScale,
26 | LinearScale,
27 | BarElement,
28 | Title,
29 | Tooltip,
30 | Legend
31 | );
32 |
33 | const barChartOptions = {
34 | responsive: true,
35 | plugins: {
36 | legend: {
37 | position: 'top' as const,
38 | },
39 | title: {
40 | display: true,
41 | text: 'Book Length Distribution'
42 | },
43 | },
44 | scales: {
45 | y: {
46 | min: 150,
47 | max: 700,
48 | ticks: {
49 | stepSize: 10
50 | }
51 | }
52 | }
53 | };
54 |
55 | function App() {
56 | const [books, setBooks] = useState([]);
57 | // const [authors, setAuthors] = useState([]);
58 | const [booksBarChartData, setBooksBarChartData] = useState>();
59 | // const [authorsBarChartData, setAuthorsBarChartData] = useState>();
60 | const [pieChartData, setPieChartData] = useState>();
61 |
62 | useEffect(() => {
63 | fetchBooks();
64 | // fetchAuthors();
65 | }, [])
66 |
67 | useEffect(() => {
68 | if (books) {
69 | const labels = books.map(book => book.title);
70 | const data = books.map(book => book.pages);
71 |
72 | setBooksBarChartData({
73 | labels,
74 | datasets: [
75 | {
76 | label: "Total Pages",
77 | data: data,
78 | backgroundColor: generateColors(data.length), // Adjust for desired number of colors
79 | borderColor: generateColors(data.length), // Adjust for desired number of colors
80 | borderWidth: 1,
81 | }
82 | ]
83 | })
84 | }
85 | }, [books]);
86 |
87 | // useEffect(() => {
88 | // if (authors) {
89 | // const labels = authors.map(author => author.name);
90 | // const data = authors.map(author => author.birthday).map(birthday => {
91 | // const today = new Date();
92 | // const diffInMs = today.getTime() - new Date(birthday).getTime();
93 | // const age = Math.floor(diffInMs / (1000 * 60 * 60 * 24 * 365));
94 | // return age;
95 | // });
96 |
97 | // setAuthorsBarChartData({
98 | // labels,
99 | // datasets: [
100 | // {
101 | // label: "Age",
102 | // data: data,
103 | // backgroundColor: 'rgba(53, 162, 235, 0.5)'
104 | // }
105 | // ]
106 | // })
107 | // }
108 | // }, [authors]);
109 |
110 | useEffect(() => {
111 | if (books) {
112 | const authorBookCount = new Map();
113 |
114 | for (const book of books) {
115 | const authorName = book.name;
116 |
117 | if (authorBookCount.has(authorName)) {
118 | authorBookCount.set(authorName, authorBookCount.get(authorName) + 1);
119 | } else {
120 | authorBookCount.set(authorName, 1);
121 | }
122 | }
123 |
124 | const chartData = {
125 | labels: Array.from(authorBookCount.keys()),
126 | datasets: [
127 | {
128 | label: 'Book Count',
129 | data: Array.from(authorBookCount.values()),
130 | backgroundColor: generateColors(authorBookCount.size), // Adjust for desired number of colors
131 | borderColor: generateColors(authorBookCount.size), // Adjust for desired number of colors
132 | borderWidth: 1,
133 | },
134 | ],
135 | };
136 |
137 | setPieChartData(chartData);
138 | }
139 | }, [books])
140 |
141 | function generateColors(numColors: number) {
142 | const colors = [];
143 | const colorPalette = ['#ff6384', '#38aecc', '#ffd700', '#4caf50', '#9c27b0'];
144 | for (let i = 0; i < numColors; i++) {
145 | colors.push(colorPalette[i % colorPalette.length]);
146 | }
147 | return colors;
148 | }
149 |
150 | const fetchBooks = async () => {
151 | try {
152 | const response = await fetch(`${API_URL}/books`);
153 | const { books, message } = await response.json();
154 |
155 | if (!response.ok) {
156 | throw new Error(message);
157 | }
158 |
159 | setBooks(books);
160 | } catch (error) {
161 | console.log(error);
162 | }
163 | };
164 |
165 | // const fetchAuthors = async () => {
166 | // try {
167 | // const response = await fetch(`${API_URL}/authors`);
168 | // const { authors, message } = await response.json();
169 |
170 | // if (!response.ok) {
171 | // throw new Error(message);
172 | // }
173 |
174 | // setAuthors(authors);
175 | // } catch (error) {
176 | // console.log(error);
177 | // }
178 | // };
179 |
180 | return (
181 |
182 |
185 |
186 |
187 |
190 |
193 |
194 |
195 |
196 | {pieChartData &&
}
197 |
198 |
199 | {booksBarChartData && ()}
202 |
203 | {/*
204 | {authorsBarChartData && ()}
205 |
*/}
206 |
207 |
208 |
209 | )
210 | }
211 |
212 | export default App
213 |
--------------------------------------------------------------------------------
/frontend/src/AuthorsPage.tsx:
--------------------------------------------------------------------------------
1 | import { Alert, Button, Table } from 'antd'
2 | import './App.css'
3 | import { useEffect, useState } from 'react'
4 | import { IconEdit } from './components/IconEdit';
5 | import { IconDelete } from './components/IconDelete';
6 | import { IconView } from './components/IconView';
7 | import { Link } from 'react-router-dom';
8 | import { Author } from './models/Author';
9 | import { AddEditAuthorModal } from './components/AddEditAuthorModal';
10 | import { ViewAuthorModal } from './components/ViewAuthorModal';
11 | import { DeleteAuthorModal } from './components/DeleteAuthorModal';
12 | const API_URL = import.meta.env.VITE_API_URL;
13 |
14 | const columns = [
15 | {
16 | title: 'ID',
17 | dataIndex: 'id',
18 | key: 'id',
19 | },
20 | {
21 | title: 'Author',
22 | dataIndex: 'name',
23 | key: 'name',
24 | },
25 | {
26 | title: 'Birthday',
27 | dataIndex: 'birthday',
28 | key: 'birthday',
29 | },
30 | {
31 | title: 'Description',
32 | dataIndex: 'bio',
33 | key: 'bio',
34 | },
35 | {
36 | title: 'Created Date',
37 | dataIndex: 'createdAt',
38 | key: 'createdAt',
39 | },
40 | {
41 | title: 'Updated Date',
42 | dataIndex: 'updatedAt',
43 | key: 'updatedAt',
44 | },
45 | {
46 | title: 'Actions',
47 | dataIndex: 'actions',
48 | key: 'actions',
49 | }
50 | ];
51 |
52 | function AuthorsPage() {
53 | const [authors, setAuthors] = useState([]);
54 | const [dataSource, setDataSource] = useState([]);
55 | const [activeAuthor, setActiveAuthor] = useState();
56 | const [isAddEditModalOpen, setIsAddEditModalOpen] = useState(false);
57 | const [isViewModalOpen, setIsViewModalOpen] = useState(false);
58 | const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
59 | const [isSuccessAlertVisible, setIsSuccessAlertVisible] = useState(false);
60 | const [isErrorAlertVisible, setIsErrorAlertVisible] = useState(false);
61 | const [message, setMessage] = useState('');
62 | const [isEdit, setIsEdit] = useState(false);
63 |
64 | useEffect(() => {
65 | fetchAuthors();
66 | }, [])
67 |
68 | useEffect(() => {
69 | formatAuthorsForDisplay(authors);
70 | // eslint-disable-next-line react-hooks/exhaustive-deps
71 | }, [authors])
72 |
73 | const fetchAuthors = async () => {
74 | try {
75 | const response = await fetch(`${API_URL}/authors`);
76 | const { authors, message } = await response.json();
77 |
78 | if (!response.ok) {
79 | throw new Error(message);
80 | }
81 |
82 | setAuthors(authors);
83 | } catch (error) {
84 | console.log(error);
85 |
86 | setMessage((error as Error).message);
87 | setIsErrorAlertVisible(true);
88 |
89 | setTimeout(() => {
90 | setIsErrorAlertVisible(false);
91 | }, 5000);
92 | }
93 | };
94 |
95 | const editAuthor = async (author: Author) => {
96 | try {
97 | if (activeAuthor) {
98 | const response = await fetch(`${API_URL}/authors/${activeAuthor.id}`, {
99 | method: 'PUT',
100 | headers: {
101 | 'Content-Type': 'application/json',
102 | },
103 | body: JSON.stringify(author),
104 | });
105 | const { message, authors } = await response.json();
106 |
107 | if (!response.ok) {
108 | throw new Error(message);
109 | }
110 |
111 | setAuthors(authors);
112 | setMessage(message);
113 | setIsSuccessAlertVisible(true);
114 |
115 | setTimeout(() => {
116 | setIsSuccessAlertVisible(false);
117 | }, 5000);
118 | }
119 | } catch (error) {
120 | console.error(error);
121 |
122 | setMessage((error as Error).message);
123 | setIsErrorAlertVisible(true);
124 |
125 | setTimeout(() => {
126 | setIsErrorAlertVisible(false);
127 | }, 5000);
128 | }
129 | }
130 |
131 | const addAuthor = async (author: Author) => {
132 | try {
133 | const response = await fetch(`${API_URL}/authors`, {
134 | method: 'POST',
135 | headers: {
136 | 'Content-Type': 'application/json',
137 | },
138 | body: JSON.stringify(author),
139 | });
140 | const { message, authors } = await response.json();
141 |
142 | if (!response.ok) {
143 | throw new Error(message);
144 | }
145 |
146 | setAuthors(authors);
147 | setMessage(message);
148 | setIsSuccessAlertVisible(true);
149 |
150 | setTimeout(() => {
151 | setIsSuccessAlertVisible(false);
152 | }, 5000);
153 | } catch (error) {
154 | console.error(error);
155 |
156 | setMessage((error as Error).message);
157 | setIsErrorAlertVisible(true);
158 |
159 | setTimeout(() => {
160 | setIsErrorAlertVisible(false);
161 | }, 5000);
162 | }
163 | }
164 |
165 | const authorAddEdit = (author: Author) => {
166 | if (isEdit) {
167 | editAuthor(author);
168 | return;
169 | }
170 |
171 | addAuthor(author);
172 | }
173 |
174 | const authorDelete = async () => {
175 | try {
176 | if (activeAuthor) {
177 | const response = await fetch(`${API_URL}/authors/${activeAuthor.id}`, {
178 | method: 'DELETE',
179 | headers: {
180 | 'Content-Type': 'application/json',
181 | }
182 | });
183 | const { message, authors } = await response.json();
184 |
185 | if (!response.ok) {
186 | throw new Error(message);
187 | }
188 |
189 | setAuthors(authors);
190 | setMessage(message);
191 | setIsSuccessAlertVisible(true);
192 |
193 | setTimeout(() => {
194 | setIsSuccessAlertVisible(false);
195 | }, 5000);
196 | }
197 | } catch (error) {
198 | console.error(error);
199 |
200 | setMessage((error as Error).message);
201 | setIsErrorAlertVisible(true);
202 |
203 | setTimeout(() => {
204 | setIsErrorAlertVisible(false);
205 | }, 5000);
206 | }
207 | }
208 |
209 | const handleAuthorAdd = () => {
210 | setActiveAuthor(undefined);
211 | setIsEdit(false);
212 | setIsAddEditModalOpen(true);
213 | }
214 |
215 | const handleAuthorEdit = (author: Author) => {
216 | setActiveAuthor(author);
217 | setIsEdit(true);
218 | setIsAddEditModalOpen(true);
219 | }
220 |
221 | const handleAuthorView = (author: Author) => {
222 | setActiveAuthor(author);
223 | setIsViewModalOpen(true);
224 | }
225 |
226 | const handleAuthorDelete = (author: Author) => {
227 | setActiveAuthor(author);
228 | setIsDeleteModalOpen(true);
229 | }
230 |
231 | const formatAuthorsForDisplay = (authors: Author[]) => {
232 | if (authors.length > 0) {
233 | const dataSource = [];
234 |
235 | for (const author of authors) {
236 | const authorObj = {
237 | key: author.id,
238 | id: author.id,
239 | name: author.name,
240 | birthday: author.birthday,
241 | bio: author.bio,
242 | createdAt: author.createdAt,
243 | updatedAt: author.updatedAt,
244 | actions: (
245 |
246 | } onClick={() => handleAuthorEdit(author)} />
247 | } onClick={() => handleAuthorView(author)} />
248 | } danger onClick={() => handleAuthorDelete(author)} />
249 |
250 | )
251 | }
252 |
253 | dataSource.push(authorObj);
254 | }
255 |
256 | setDataSource(dataSource);
257 | }
258 | }
259 |
260 | return (
261 |
262 |
263 |
266 | MANAGE AUTHORS
267 |
268 |
269 |
270 |
273 | {isSuccessAlertVisible && (
274 |
280 | )}
281 | {isErrorAlertVisible && (
282 |
288 | )}
289 |
290 |
293 |
294 |
295 |
296 |
297 |
298 | )
299 | }
300 |
301 | export default AuthorsPage
302 |
--------------------------------------------------------------------------------
/frontend/src/BooksPage.tsx:
--------------------------------------------------------------------------------
1 | import { Alert, Button, Table } from 'antd'
2 | import './App.css'
3 | import { useEffect, useState } from 'react'
4 | import { IconEdit } from './components/IconEdit';
5 | import { IconDelete } from './components/IconDelete';
6 | import { IconView } from './components/IconView';
7 | import { Link } from 'react-router-dom';
8 | import { AddEditBookModal } from './components/AddEditBookModal';
9 | import { ViewBookModal } from './components/ViewBookModal';
10 | import { DeleteBookModal } from './components/DeleteBookModal';
11 | import { Book, BookDTO, BookFormDTO } from './models/Books';
12 | import { Author } from './models/Author';
13 | const API_URL = import.meta.env.VITE_API_URL;
14 |
15 | const columns = [
16 | {
17 | title: 'ID',
18 | dataIndex: 'id',
19 | key: 'id',
20 | },
21 | {
22 | title: 'Title',
23 | dataIndex: 'title',
24 | key: 'title',
25 | },
26 | {
27 | title: 'Description',
28 | dataIndex: 'description',
29 | key: 'description',
30 | },
31 | {
32 | title: 'Release Date',
33 | dataIndex: 'releaseDate',
34 | key: 'releaseDate',
35 | },
36 | {
37 | title: 'Author',
38 | dataIndex: 'author',
39 | key: 'author',
40 | },
41 | {
42 | title: 'Created Date',
43 | dataIndex: 'createdAt',
44 | key: 'createdAt',
45 | },
46 | {
47 | title: 'Updated Date',
48 | dataIndex: 'updatedAt',
49 | key: 'updatedAt',
50 | },
51 | {
52 | title: 'Actions',
53 | dataIndex: 'actions',
54 | key: 'actions',
55 | },
56 | ];
57 |
58 | function BooksPage() {
59 | const [books, setBooks] = useState([]);
60 | const [authors, setAuthors] = useState([]);
61 | const [dataSource, setDataSource] = useState([]);
62 | const [activeBook, setActiveBook] = useState();
63 | const [isAddEditModalOpen, setIsAddEditModalOpen] = useState(false);
64 | const [isViewModalOpen, setIsViewModalOpen] = useState(false);
65 | const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
66 | const [isSuccessAlertVisible, setIsSuccessAlertVisible] = useState(false);
67 | const [isErrorAlertVisible, setIsErrorAlertVisible] = useState(false);
68 | const [message, setMessage] = useState('');
69 | const [isEdit, setIsEdit] = useState(false);
70 |
71 | useEffect(() => {
72 | fetchBooks();
73 | fetchAuthors();
74 | }, [])
75 |
76 | useEffect(() => {
77 | formatBooksForDisplay(books);
78 | // eslint-disable-next-line react-hooks/exhaustive-deps
79 | }, [books])
80 |
81 | const fetchBooks = async () => {
82 | try {
83 | const response = await fetch(`${API_URL}/books`);
84 | const { books, message } = await response.json();
85 |
86 | if (!response.ok) {
87 | throw new Error(message);
88 | }
89 |
90 | setBooks(books);
91 | } catch (error) {
92 | console.log(error);
93 |
94 | setMessage((error as Error).message);
95 | setIsErrorAlertVisible(true);
96 |
97 | setTimeout(() => {
98 | setIsErrorAlertVisible(false);
99 | }, 5000);
100 | }
101 | };
102 |
103 | const fetchAuthors = async () => {
104 | try {
105 | const response = await fetch(`${API_URL}/authors`);
106 | const { authors, message } = await response.json();
107 |
108 | if (!response.ok) {
109 | throw new Error(message);
110 | }
111 |
112 | setAuthors(authors);
113 | } catch (error) {
114 | console.log(error);
115 |
116 | setMessage((error as Error).message);
117 | setIsErrorAlertVisible(true);
118 |
119 | setTimeout(() => {
120 | setIsErrorAlertVisible(false);
121 | }, 5000);
122 | }
123 | };
124 |
125 | const editBook = async (book: BookFormDTO) => {
126 | try {
127 | if (activeBook) {
128 | const response = await fetch(`${API_URL}/books/${activeBook.id}`, {
129 | method: 'PUT',
130 | headers: {
131 | 'Content-Type': 'application/json',
132 | },
133 | body: JSON.stringify(book),
134 | });
135 |
136 | const { message, books } = await response.json();
137 |
138 | if (!response.ok) {
139 | throw new Error(message);
140 | }
141 |
142 | setBooks(books);
143 | setMessage(message);
144 | setIsSuccessAlertVisible(true);
145 |
146 | setTimeout(() => {
147 | setIsSuccessAlertVisible(false);
148 | }, 5000);
149 | }
150 | } catch (error) {
151 | console.error(error);
152 |
153 | setMessage((error as Error).message);
154 | setIsErrorAlertVisible(true);
155 |
156 | setTimeout(() => {
157 | setIsErrorAlertVisible(false);
158 | }, 5000);
159 | }
160 | }
161 |
162 | const addBook = async (book: BookFormDTO) => {
163 | try {
164 | const response = await fetch(`${API_URL}/books`, {
165 | method: 'POST',
166 | headers: {
167 | 'Content-Type': 'application/json',
168 | },
169 | body: JSON.stringify(book),
170 | });
171 |
172 | const { message, books } = await response.json();
173 |
174 | if (!response.ok) {
175 | throw new Error(message);
176 | }
177 |
178 | setBooks(books);
179 | setMessage(message);
180 | setIsSuccessAlertVisible(true);
181 |
182 | setTimeout(() => {
183 | setIsSuccessAlertVisible(false);
184 | }, 5000);
185 | } catch (error) {
186 | console.error(error);
187 |
188 | setMessage((error as Error).message);
189 | setIsErrorAlertVisible(true);
190 |
191 | setTimeout(() => {
192 | setIsErrorAlertVisible(false);
193 | }, 5000);
194 | }
195 | }
196 |
197 | const bookAddEdit = (book: BookFormDTO) => {
198 | if (isEdit) {
199 | editBook(book);
200 | return;
201 | }
202 |
203 | addBook(book);
204 | }
205 |
206 | const bookDelete = async () => {
207 | try {
208 | if (activeBook) {
209 | const response = await fetch(`${API_URL}/books/${activeBook.id}`, {
210 | method: 'DELETE',
211 | headers: {
212 | 'Content-Type': 'application/json',
213 | }
214 | });
215 |
216 | const { message, books } = await response.json();
217 |
218 | if (!response.ok) {
219 | throw new Error(message);
220 | }
221 |
222 | setBooks(books);
223 | setMessage(message);
224 | setIsSuccessAlertVisible(true);
225 |
226 | setTimeout(() => {
227 | setIsSuccessAlertVisible(false);
228 | }, 5000);
229 | }
230 | } catch (error) {
231 | console.error(error);
232 |
233 | setMessage((error as Error).message);
234 | setIsErrorAlertVisible(true);
235 |
236 | setTimeout(() => {
237 | setIsErrorAlertVisible(false);
238 | }, 5000);
239 | }
240 | }
241 |
242 | const handleBookAdd = () => {
243 | setActiveBook(undefined);
244 | setIsEdit(false);
245 | setIsAddEditModalOpen(true);
246 | }
247 |
248 | const handleBookEdit = (book: Book) => {
249 | setActiveBook(book);
250 | setIsEdit(true);
251 | setIsAddEditModalOpen(true);
252 | }
253 |
254 | const handleBookView = (book: Book) => {
255 | setActiveBook(book);
256 | setIsViewModalOpen(true);
257 | }
258 |
259 | const handleBookDelete = (book: Book) => {
260 | setActiveBook(book);
261 | setIsDeleteModalOpen(true);
262 | }
263 |
264 | const formatBooksForDisplay = (books: Book[]) => {
265 | if (books.length > 0) {
266 | const dataSource: BookDTO[] = [];
267 |
268 | for (const book of books) {
269 | const bookObj = {
270 | key: book.id,
271 | id: book.id,
272 | title: book.title,
273 | releaseDate: book.releaseDate,
274 | description: book.description,
275 | pages: book.pages,
276 | author: book?.name,
277 | createdAt: book.createdAt,
278 | updatedAt: book.updatedAt,
279 | actions: (
280 |
281 | } onClick={() => handleBookEdit(book)} />
282 | } onClick={() => handleBookView(book)} />
283 | } danger onClick={() => handleBookDelete(book)} />
284 |
285 | )
286 | }
287 |
288 | dataSource.push(bookObj);
289 | }
290 |
291 | setDataSource(dataSource);
292 | }
293 | }
294 |
295 | return (
296 |
297 |
298 |
301 | MANAGE BOOKS
302 |
303 |
304 |
305 |
308 | {isSuccessAlertVisible && (
309 |
315 | )}
316 | {isErrorAlertVisible && (
317 |
323 | )}
324 |
325 |
328 |
329 |
337 |
338 |
339 |
340 | )
341 | }
342 |
343 | export default BooksPage
344 |
--------------------------------------------------------------------------------
/frontend/db.json:
--------------------------------------------------------------------------------
1 | {
2 | "books": [
3 | {
4 | "id": 1,
5 | "title": "Harry Potter and the Sorcerer's Stone",
6 | "releaseDate": "1997-06-26",
7 | "description": "On his birthday, Harry Potter discovers that he is the son of two well-known wizards, from whom he has inherited magical powers. He must attend a famous school of magic and sorcery, where he establishes a friendship with two young men who will become his companions on his adventure. During his first year at Hogwarts, he discovers that a malevolent and powerful wizard named Voldemort is in search of a philosopher's stone that prolongs the life of its owner.",
8 | "pages": 223,
9 | "name": "J.K. Rowling (Joanne Kathleen Rowling)",
10 | "birthday": "1965-07-31",
11 | "bio": "J.K. Rowling is a British author best known for writing the Harry Potter fantasy series. The series has won multiple awards and sold over 500 million copies, becoming the best-selling book series in history. Rowling has also written other novels, including The Casual Vacancy and the Cormoran Strike crime series under the pen name Robert Galbraith."
12 | },
13 | {
14 | "id": 2,
15 | "title": "Harry Potter and the chamber of secrets",
16 | "releaseDate": "1998-07-02",
17 | "description": "Harry Potter and the sophomores investigate a malevolent threat to their Hogwarts classmates, a menacing beast that hides within the castle.",
18 | "pages": 251,
19 | "name": "J.K. Rowling (Joanne Kathleen Rowling)",
20 | "birthday": "1965-07-31",
21 | "bio": "J.K. Rowling is a British author best known for writing the Harry Potter fantasy series. The series has won multiple awards and sold over 500 million copies, becoming the best-selling book series in history. Rowling has also written other novels, including The Casual Vacancy and the Cormoran Strike crime series under the pen name Robert Galbraith."
22 | },
23 | {
24 | "id": 3,
25 | "title": "Harry Potter and the Prisoner of Azkaban",
26 | "releaseDate": "1999-07-08",
27 | "description": "Harry's third year of studies at Hogwarts is threatened by Sirius Black's escape from Azkaban prison. Apparently, it is a dangerous wizard who was an accomplice of Lord Voldemort and who will try to take revenge on Harry Potter.",
28 | "pages": 317,
29 | "name": "J.K. Rowling (Joanne Kathleen Rowling)",
30 | "birthday": "1965-07-31",
31 | "bio": "J.K. Rowling is a British author best known for writing the Harry Potter fantasy series. The series has won multiple awards and sold over 500 million copies, becoming the best-selling book series in history. Rowling has also written other novels, including The Casual Vacancy and the Cormoran Strike crime series under the pen name Robert Galbraith."
32 | },
33 | {
34 | "id": 4,
35 | "title": "Harry Potter and the Goblet of Fire",
36 | "releaseDate": "2000-07-08",
37 | "description": "Hogwarts prepares for the Triwizard Tournament, in which three schools of wizardry will compete. To everyone's surprise, Harry Potter is chosen to participate in the competition, in which he must fight dragons, enter the water and face his greatest fears.",
38 | "pages": 636,
39 | "name": "J.K. Rowling (Joanne Kathleen Rowling)",
40 | "birthday": "1965-07-31",
41 | "bio": "J.K. Rowling is a British author best known for writing the Harry Potter fantasy series. The series has won multiple awards and sold over 500 million copies, becoming the best-selling book series in history. Rowling has also written other novels, including The Casual Vacancy and the Cormoran Strike crime series under the pen name Robert Galbraith."
42 | },
43 | {
44 | "id": 5,
45 | "title": "Harry Potter and the Order of the Phoenix",
46 | "releaseDate": "2003-06-21",
47 | "description": "In his fifth year at Hogwarts, Harry discovers that many members of the wizarding community do not know the truth about his encounter with Lord Voldemort. Cornelius Fudge, Minister of Magic, appoints Dolores Umbridge as Defense Against the Dark Arts teacher because he believes that Professor Dumbledore plans to take over his job. But his teachings are inadequate, so Harry prepares the students to defend the school against evil.",
48 | "pages": 766,
49 | "name": "J.K. Rowling (Joanne Kathleen Rowling)",
50 | "birthday": "1965-07-31",
51 | "bio": "J.K. Rowling is a British author best known for writing the Harry Potter fantasy series. The series has won multiple awards and sold over 500 million copies, becoming the best-selling book series in history. Rowling has also written other novels, including The Casual Vacancy and the Cormoran Strike crime series under the pen name Robert Galbraith."
52 | },
53 | {
54 | "id": 6,
55 | "title": "Harry Potter and the Half-Blood Prince",
56 | "releaseDate": "2005-07-16",
57 | "description": "Harry discovers a powerful book and, while trying to discover its origins, collaborates with Dumbledore in the search for a series of magical objects that will aid in the destruction of Lord Voldemort.",
58 | "pages": 607,
59 | "name": "J.K. Rowling (Joanne Kathleen Rowling)",
60 | "birthday": "1965-07-31",
61 | "bio": "J.K. Rowling is a British author best known for writing the Harry Potter fantasy series. The series has won multiple awards and sold over 500 million copies, becoming the best-selling book series in history. Rowling has also written other novels, including The Casual Vacancy and the Cormoran Strike crime series under the pen name Robert Galbraith."
62 | },
63 | {
64 | "id": 7,
65 | "title": "Harry Potter and the Deathly Hallows",
66 | "releaseDate": "Jul 21, 2007",
67 | "description": "Harry, Ron and Hermione go on a dangerous mission to locate and destroy the secret of Voldemort's immortality and destruction - the Horcruces. Alone, without the guidance of their teachers or the protection of Professor Dumbledore, the three friends must lean on each other more than ever. But there are Dark Forces in between that threaten to tear them apart. Harry Potter is getting closer and closer to the task for which he has been preparing since the first day he set foot in Hogwarts: the last battle with Voldemort.",
68 | "pages": 607,
69 | "name": "J.K. Rowling (Joanne Kathleen Rowling)",
70 | "birthday": "1965-07-31",
71 | "bio": "J.K. Rowling is a British author best known for writing the Harry Potter fantasy series. The series has won multiple awards and sold over 500 million copies, becoming the best-selling book series in history. Rowling has also written other novels, including The Casual Vacancy and the Cormoran Strike crime series under the pen name Robert Galbraith."
72 | },
73 | {
74 | "id": 8,
75 | "title": "Harry Potter and the Cursed Child",
76 | "releaseDate": "2016-07-30",
77 | "description": "Harry's second son entered Hogwarts, but in Slytherin. His relationship with Harry is getting worse and he became close friends with Draco's son, Scorpius Malfoy who is said to be Lord Voldemort's son.",
78 | "pages": 336,
79 | "name": "J.K. Rowling (Joanne Kathleen Rowling)",
80 | "birthday": "1965-07-31",
81 | "bio": "J.K. Rowling is a British author best known for writing the Harry Potter fantasy series. The series has won multiple awards and sold over 500 million copies, becoming the best-selling book series in history. Rowling has also written other novels, including The Casual Vacancy and the Cormoran Strike crime series under the pen name Robert Galbraith."
82 | },
83 | {
84 | "id": 9,
85 | "title": "Harry Potter and the Cursed Child",
86 | "releaseDate": "2016-07-30",
87 | "description": "Harry's second son entered Hogwarts, but in Slytherin. His relationship with Harry is getting worse and he became close friends with Draco's son, Scorpius Malfoy who is said to be Lord Voldemort's son.",
88 | "pages": 336,
89 | "name": "J.K. Rowling (Joanne Kathleen Rowling)",
90 | "birthday": "1965-07-31",
91 | "bio": "J.K. Rowling is a British author best known for writing the Harry Potter fantasy series. The series has won multiple awards and sold over 500 million copies, becoming the best-selling book series in history. Rowling has also written other novels, including The Casual Vacancy and the Cormoran Strike crime series under the pen name Robert Galbraith."
92 | },
93 | {
94 | "id": 10,
95 | "title": "Pride and Prejudice",
96 | "releaseDate": "1813-01-28",
97 | "description": "An English novel of manners by Jane Austen, first published in 1813. The story centres on the relationships among the Bennet sisters, in particular Elizabeth Bennet the middle daughter, and the wealthy Mr. Darcy. Austen satirizes the social classes of the English gentry through a witty and ironic narrative voice.",
98 | "pages": 224,
99 | "name": "Jane Austen",
100 | "birthday": "Dec 16, 1775",
101 | "bio": "Jane Austen was an English novelist known for her wit, social commentary, and romantic stories. Her six major novels, which explore themes of love, marriage, and money, have earned her a place as one of the greatest writers in the English language."
102 | },
103 | {
104 | "id": 11,
105 | "title": "To Kill a Mockingbird",
106 | "releaseDate": "1960-07-11",
107 | "description": "The Pulitzer Prize-winning novel tells the story of Scout Finch, a young girl growing up in Alabama during the Great Depression. Scout's father, Atticus Finch, is a lawyer who defends Tom Robinson, a black man wrongly accused of assaulting a white woman. The novel explores themes of racial injustice, prejudice, and the importance of courage and compassion.",
108 | "pages": 324,
109 | "name": "Harper Lee",
110 | "birthday": "Apr 28, 1926",
111 | "bio": "Harper Lee was an American novelist best known for her Pulitzer Prize-winning novel To Kill a Mockingbird. The novel explores themes of racial injustice and the importance of compassion. Lee published a sequel, Go Set a Watchman, in 2015."
112 | },
113 | {
114 | "id": 12,
115 | "title": "The Lord of the Rings: The Fellowship of the Ring",
116 | "releaseDate": "Jul 29, 1954",
117 | "description": "The first book in J.R.R. Tolkien's epic fantasy trilogy, The Lord of the Rings. The Fellowship of the Ring follows hobbit Frodo Baggins as he inherits the One Ring, an evil artifact of power created by the Dark Lord Sauron. Frodo embarks on a quest to destroy the Ring in the fires of Mount Doom, accompanied by a fellowship of eight companions.",
118 | "pages": 482,
119 | "name": "J.R.R. Tolkien",
120 | "birthday": "1892-01-03",
121 | "bio": "J.R.R. Tolkien was a British philologist and writer best known for his fantasy novels The Hobbit and The Lord of the Rings. Tolkien's works have had a profound influence on the fantasy genre and popular culture."
122 | },
123 | {
124 | "id": 13,
125 | "title": "Frankenstein; or, The Modern Prometheus",
126 | "releaseDate": "1818-03-03",
127 | "description": "A Gothic novel by Mary Shelley that tells the story of Victor Frankenstein, a young scientist who creates a grotesque creature in an unorthodox scientific experiment. Frankenstein is horrified by his creation and abandons it, but the creature seeks revenge. The novel explores themes of scientific responsibility, creation versus destruction, and the nature of good and evil.",
128 | "pages": 211,
129 | "name": "Mary Shelley",
130 | "birthday": "Aug 31, 1797",
131 | "bio": "Mary Shelley was a British novelist, playwright, and short story writer, the daughter of Mary Wollstonecraft Godwin and the wife of poet Percy Bysshe Shelley. Frankenstein, or, The Modern Prometheus (1818) is her most famous work."
132 | },
133 | {
134 | "id": 14,
135 | "title": "The Hitchhiker's Guide to the Galaxy",
136 | "releaseDate": "1979-10-12",
137 | "description": "A comic science fiction comedy series created by Douglas Adams. The story follows the comedic misadventures of Arthur Dent, a hapless Englishman, following the destruction of the Earth by the Vogons, a race of unpleasant bureaucratic aliens. Arthur escapes with his friend Ford Prefect, who reveals himself to be an undercover researcher for the titular Hitchhiker's Guide to the Galaxy, a galactic encyclopedia containing information about anything and everything.",
138 | "pages": 184,
139 | "name": "Douglas Adams",
140 | "birthday": "Mar 11, 1952",
141 | "bio": "Douglas Adams was an English science fiction writer, satirist, humorist, dramatist, screenwriter, and occasional actor. He is best known for the Hitchhiker's Guide to the Galaxy comedy series, which inspired a radio comedy, several books, stage shows, comic books, a 1981 TV series, a 1984 video game, a 2005 feature film, and a 2008 sequel film."
142 | }
143 | ]
144 | }
145 |
--------------------------------------------------------------------------------
/backend/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mysql-express",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "mysql-express",
9 | "version": "1.0.0",
10 | "license": "ISC",
11 | "dependencies": {
12 | "body-parser": "^1.20.2",
13 | "cors": "^2.8.5",
14 | "express": "^4.19.2",
15 | "mysql2": "^3.10.0"
16 | }
17 | },
18 | "node_modules/accepts": {
19 | "version": "1.3.8",
20 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
21 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
22 | "dependencies": {
23 | "mime-types": "~2.1.34",
24 | "negotiator": "0.6.3"
25 | },
26 | "engines": {
27 | "node": ">= 0.6"
28 | }
29 | },
30 | "node_modules/array-flatten": {
31 | "version": "1.1.1",
32 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
33 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
34 | },
35 | "node_modules/body-parser": {
36 | "version": "1.20.2",
37 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
38 | "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
39 | "dependencies": {
40 | "bytes": "3.1.2",
41 | "content-type": "~1.0.5",
42 | "debug": "2.6.9",
43 | "depd": "2.0.0",
44 | "destroy": "1.2.0",
45 | "http-errors": "2.0.0",
46 | "iconv-lite": "0.4.24",
47 | "on-finished": "2.4.1",
48 | "qs": "6.11.0",
49 | "raw-body": "2.5.2",
50 | "type-is": "~1.6.18",
51 | "unpipe": "1.0.0"
52 | },
53 | "engines": {
54 | "node": ">= 0.8",
55 | "npm": "1.2.8000 || >= 1.4.16"
56 | }
57 | },
58 | "node_modules/bytes": {
59 | "version": "3.1.2",
60 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
61 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
62 | "engines": {
63 | "node": ">= 0.8"
64 | }
65 | },
66 | "node_modules/call-bind": {
67 | "version": "1.0.7",
68 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
69 | "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
70 | "dependencies": {
71 | "es-define-property": "^1.0.0",
72 | "es-errors": "^1.3.0",
73 | "function-bind": "^1.1.2",
74 | "get-intrinsic": "^1.2.4",
75 | "set-function-length": "^1.2.1"
76 | },
77 | "engines": {
78 | "node": ">= 0.4"
79 | },
80 | "funding": {
81 | "url": "https://github.com/sponsors/ljharb"
82 | }
83 | },
84 | "node_modules/content-disposition": {
85 | "version": "0.5.4",
86 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
87 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
88 | "dependencies": {
89 | "safe-buffer": "5.2.1"
90 | },
91 | "engines": {
92 | "node": ">= 0.6"
93 | }
94 | },
95 | "node_modules/content-type": {
96 | "version": "1.0.5",
97 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
98 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
99 | "engines": {
100 | "node": ">= 0.6"
101 | }
102 | },
103 | "node_modules/cookie": {
104 | "version": "0.6.0",
105 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
106 | "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
107 | "engines": {
108 | "node": ">= 0.6"
109 | }
110 | },
111 | "node_modules/cookie-signature": {
112 | "version": "1.0.6",
113 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
114 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
115 | },
116 | "node_modules/cors": {
117 | "version": "2.8.5",
118 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
119 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
120 | "dependencies": {
121 | "object-assign": "^4",
122 | "vary": "^1"
123 | },
124 | "engines": {
125 | "node": ">= 0.10"
126 | }
127 | },
128 | "node_modules/debug": {
129 | "version": "2.6.9",
130 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
131 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
132 | "dependencies": {
133 | "ms": "2.0.0"
134 | }
135 | },
136 | "node_modules/define-data-property": {
137 | "version": "1.1.4",
138 | "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
139 | "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
140 | "dependencies": {
141 | "es-define-property": "^1.0.0",
142 | "es-errors": "^1.3.0",
143 | "gopd": "^1.0.1"
144 | },
145 | "engines": {
146 | "node": ">= 0.4"
147 | },
148 | "funding": {
149 | "url": "https://github.com/sponsors/ljharb"
150 | }
151 | },
152 | "node_modules/denque": {
153 | "version": "2.1.0",
154 | "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
155 | "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
156 | "engines": {
157 | "node": ">=0.10"
158 | }
159 | },
160 | "node_modules/depd": {
161 | "version": "2.0.0",
162 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
163 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
164 | "engines": {
165 | "node": ">= 0.8"
166 | }
167 | },
168 | "node_modules/destroy": {
169 | "version": "1.2.0",
170 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
171 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
172 | "engines": {
173 | "node": ">= 0.8",
174 | "npm": "1.2.8000 || >= 1.4.16"
175 | }
176 | },
177 | "node_modules/ee-first": {
178 | "version": "1.1.1",
179 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
180 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
181 | },
182 | "node_modules/encodeurl": {
183 | "version": "1.0.2",
184 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
185 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
186 | "engines": {
187 | "node": ">= 0.8"
188 | }
189 | },
190 | "node_modules/es-define-property": {
191 | "version": "1.0.0",
192 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
193 | "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
194 | "dependencies": {
195 | "get-intrinsic": "^1.2.4"
196 | },
197 | "engines": {
198 | "node": ">= 0.4"
199 | }
200 | },
201 | "node_modules/es-errors": {
202 | "version": "1.3.0",
203 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
204 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
205 | "engines": {
206 | "node": ">= 0.4"
207 | }
208 | },
209 | "node_modules/escape-html": {
210 | "version": "1.0.3",
211 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
212 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
213 | },
214 | "node_modules/etag": {
215 | "version": "1.8.1",
216 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
217 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
218 | "engines": {
219 | "node": ">= 0.6"
220 | }
221 | },
222 | "node_modules/express": {
223 | "version": "4.19.2",
224 | "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
225 | "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
226 | "dependencies": {
227 | "accepts": "~1.3.8",
228 | "array-flatten": "1.1.1",
229 | "body-parser": "1.20.2",
230 | "content-disposition": "0.5.4",
231 | "content-type": "~1.0.4",
232 | "cookie": "0.6.0",
233 | "cookie-signature": "1.0.6",
234 | "debug": "2.6.9",
235 | "depd": "2.0.0",
236 | "encodeurl": "~1.0.2",
237 | "escape-html": "~1.0.3",
238 | "etag": "~1.8.1",
239 | "finalhandler": "1.2.0",
240 | "fresh": "0.5.2",
241 | "http-errors": "2.0.0",
242 | "merge-descriptors": "1.0.1",
243 | "methods": "~1.1.2",
244 | "on-finished": "2.4.1",
245 | "parseurl": "~1.3.3",
246 | "path-to-regexp": "0.1.7",
247 | "proxy-addr": "~2.0.7",
248 | "qs": "6.11.0",
249 | "range-parser": "~1.2.1",
250 | "safe-buffer": "5.2.1",
251 | "send": "0.18.0",
252 | "serve-static": "1.15.0",
253 | "setprototypeof": "1.2.0",
254 | "statuses": "2.0.1",
255 | "type-is": "~1.6.18",
256 | "utils-merge": "1.0.1",
257 | "vary": "~1.1.2"
258 | },
259 | "engines": {
260 | "node": ">= 0.10.0"
261 | }
262 | },
263 | "node_modules/finalhandler": {
264 | "version": "1.2.0",
265 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
266 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
267 | "dependencies": {
268 | "debug": "2.6.9",
269 | "encodeurl": "~1.0.2",
270 | "escape-html": "~1.0.3",
271 | "on-finished": "2.4.1",
272 | "parseurl": "~1.3.3",
273 | "statuses": "2.0.1",
274 | "unpipe": "~1.0.0"
275 | },
276 | "engines": {
277 | "node": ">= 0.8"
278 | }
279 | },
280 | "node_modules/forwarded": {
281 | "version": "0.2.0",
282 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
283 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
284 | "engines": {
285 | "node": ">= 0.6"
286 | }
287 | },
288 | "node_modules/fresh": {
289 | "version": "0.5.2",
290 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
291 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
292 | "engines": {
293 | "node": ">= 0.6"
294 | }
295 | },
296 | "node_modules/function-bind": {
297 | "version": "1.1.2",
298 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
299 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
300 | "funding": {
301 | "url": "https://github.com/sponsors/ljharb"
302 | }
303 | },
304 | "node_modules/generate-function": {
305 | "version": "2.3.1",
306 | "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
307 | "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
308 | "dependencies": {
309 | "is-property": "^1.0.2"
310 | }
311 | },
312 | "node_modules/get-intrinsic": {
313 | "version": "1.2.4",
314 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
315 | "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
316 | "dependencies": {
317 | "es-errors": "^1.3.0",
318 | "function-bind": "^1.1.2",
319 | "has-proto": "^1.0.1",
320 | "has-symbols": "^1.0.3",
321 | "hasown": "^2.0.0"
322 | },
323 | "engines": {
324 | "node": ">= 0.4"
325 | },
326 | "funding": {
327 | "url": "https://github.com/sponsors/ljharb"
328 | }
329 | },
330 | "node_modules/gopd": {
331 | "version": "1.0.1",
332 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
333 | "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
334 | "dependencies": {
335 | "get-intrinsic": "^1.1.3"
336 | },
337 | "funding": {
338 | "url": "https://github.com/sponsors/ljharb"
339 | }
340 | },
341 | "node_modules/has-property-descriptors": {
342 | "version": "1.0.2",
343 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
344 | "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
345 | "dependencies": {
346 | "es-define-property": "^1.0.0"
347 | },
348 | "funding": {
349 | "url": "https://github.com/sponsors/ljharb"
350 | }
351 | },
352 | "node_modules/has-proto": {
353 | "version": "1.0.3",
354 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
355 | "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
356 | "engines": {
357 | "node": ">= 0.4"
358 | },
359 | "funding": {
360 | "url": "https://github.com/sponsors/ljharb"
361 | }
362 | },
363 | "node_modules/has-symbols": {
364 | "version": "1.0.3",
365 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
366 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
367 | "engines": {
368 | "node": ">= 0.4"
369 | },
370 | "funding": {
371 | "url": "https://github.com/sponsors/ljharb"
372 | }
373 | },
374 | "node_modules/hasown": {
375 | "version": "2.0.2",
376 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
377 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
378 | "dependencies": {
379 | "function-bind": "^1.1.2"
380 | },
381 | "engines": {
382 | "node": ">= 0.4"
383 | }
384 | },
385 | "node_modules/http-errors": {
386 | "version": "2.0.0",
387 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
388 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
389 | "dependencies": {
390 | "depd": "2.0.0",
391 | "inherits": "2.0.4",
392 | "setprototypeof": "1.2.0",
393 | "statuses": "2.0.1",
394 | "toidentifier": "1.0.1"
395 | },
396 | "engines": {
397 | "node": ">= 0.8"
398 | }
399 | },
400 | "node_modules/iconv-lite": {
401 | "version": "0.4.24",
402 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
403 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
404 | "dependencies": {
405 | "safer-buffer": ">= 2.1.2 < 3"
406 | },
407 | "engines": {
408 | "node": ">=0.10.0"
409 | }
410 | },
411 | "node_modules/inherits": {
412 | "version": "2.0.4",
413 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
414 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
415 | },
416 | "node_modules/ipaddr.js": {
417 | "version": "1.9.1",
418 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
419 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
420 | "engines": {
421 | "node": ">= 0.10"
422 | }
423 | },
424 | "node_modules/is-property": {
425 | "version": "1.0.2",
426 | "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
427 | "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="
428 | },
429 | "node_modules/long": {
430 | "version": "5.2.3",
431 | "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
432 | "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q=="
433 | },
434 | "node_modules/lru-cache": {
435 | "version": "8.0.5",
436 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz",
437 | "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==",
438 | "engines": {
439 | "node": ">=16.14"
440 | }
441 | },
442 | "node_modules/media-typer": {
443 | "version": "0.3.0",
444 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
445 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
446 | "engines": {
447 | "node": ">= 0.6"
448 | }
449 | },
450 | "node_modules/merge-descriptors": {
451 | "version": "1.0.1",
452 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
453 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
454 | },
455 | "node_modules/methods": {
456 | "version": "1.1.2",
457 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
458 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
459 | "engines": {
460 | "node": ">= 0.6"
461 | }
462 | },
463 | "node_modules/mime": {
464 | "version": "1.6.0",
465 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
466 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
467 | "bin": {
468 | "mime": "cli.js"
469 | },
470 | "engines": {
471 | "node": ">=4"
472 | }
473 | },
474 | "node_modules/mime-db": {
475 | "version": "1.52.0",
476 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
477 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
478 | "engines": {
479 | "node": ">= 0.6"
480 | }
481 | },
482 | "node_modules/mime-types": {
483 | "version": "2.1.35",
484 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
485 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
486 | "dependencies": {
487 | "mime-db": "1.52.0"
488 | },
489 | "engines": {
490 | "node": ">= 0.6"
491 | }
492 | },
493 | "node_modules/ms": {
494 | "version": "2.0.0",
495 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
496 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
497 | },
498 | "node_modules/mysql2": {
499 | "version": "3.10.0",
500 | "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.10.0.tgz",
501 | "integrity": "sha512-qx0mfWYt1DpTPkw8mAcHW/OwqqyNqBLBHvY5IjN8+icIYTjt6znrgYJ+gxqNNRpVknb5Wc/gcCM4XjbCR0j5tw==",
502 | "dependencies": {
503 | "denque": "^2.1.0",
504 | "generate-function": "^2.3.1",
505 | "iconv-lite": "^0.6.3",
506 | "long": "^5.2.1",
507 | "lru-cache": "^8.0.0",
508 | "named-placeholders": "^1.1.3",
509 | "seq-queue": "^0.0.5",
510 | "sqlstring": "^2.3.2"
511 | },
512 | "engines": {
513 | "node": ">= 8.0"
514 | }
515 | },
516 | "node_modules/mysql2/node_modules/iconv-lite": {
517 | "version": "0.6.3",
518 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
519 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
520 | "dependencies": {
521 | "safer-buffer": ">= 2.1.2 < 3.0.0"
522 | },
523 | "engines": {
524 | "node": ">=0.10.0"
525 | }
526 | },
527 | "node_modules/mysql2/node_modules/sqlstring": {
528 | "version": "2.3.3",
529 | "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
530 | "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
531 | "engines": {
532 | "node": ">= 0.6"
533 | }
534 | },
535 | "node_modules/named-placeholders": {
536 | "version": "1.1.3",
537 | "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz",
538 | "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==",
539 | "dependencies": {
540 | "lru-cache": "^7.14.1"
541 | },
542 | "engines": {
543 | "node": ">=12.0.0"
544 | }
545 | },
546 | "node_modules/named-placeholders/node_modules/lru-cache": {
547 | "version": "7.18.3",
548 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
549 | "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
550 | "engines": {
551 | "node": ">=12"
552 | }
553 | },
554 | "node_modules/negotiator": {
555 | "version": "0.6.3",
556 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
557 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
558 | "engines": {
559 | "node": ">= 0.6"
560 | }
561 | },
562 | "node_modules/object-assign": {
563 | "version": "4.1.1",
564 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
565 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
566 | "engines": {
567 | "node": ">=0.10.0"
568 | }
569 | },
570 | "node_modules/object-inspect": {
571 | "version": "1.13.1",
572 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
573 | "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
574 | "funding": {
575 | "url": "https://github.com/sponsors/ljharb"
576 | }
577 | },
578 | "node_modules/on-finished": {
579 | "version": "2.4.1",
580 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
581 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
582 | "dependencies": {
583 | "ee-first": "1.1.1"
584 | },
585 | "engines": {
586 | "node": ">= 0.8"
587 | }
588 | },
589 | "node_modules/parseurl": {
590 | "version": "1.3.3",
591 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
592 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
593 | "engines": {
594 | "node": ">= 0.8"
595 | }
596 | },
597 | "node_modules/path-to-regexp": {
598 | "version": "0.1.7",
599 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
600 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
601 | },
602 | "node_modules/proxy-addr": {
603 | "version": "2.0.7",
604 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
605 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
606 | "dependencies": {
607 | "forwarded": "0.2.0",
608 | "ipaddr.js": "1.9.1"
609 | },
610 | "engines": {
611 | "node": ">= 0.10"
612 | }
613 | },
614 | "node_modules/qs": {
615 | "version": "6.11.0",
616 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
617 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
618 | "dependencies": {
619 | "side-channel": "^1.0.4"
620 | },
621 | "engines": {
622 | "node": ">=0.6"
623 | },
624 | "funding": {
625 | "url": "https://github.com/sponsors/ljharb"
626 | }
627 | },
628 | "node_modules/range-parser": {
629 | "version": "1.2.1",
630 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
631 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
632 | "engines": {
633 | "node": ">= 0.6"
634 | }
635 | },
636 | "node_modules/raw-body": {
637 | "version": "2.5.2",
638 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
639 | "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
640 | "dependencies": {
641 | "bytes": "3.1.2",
642 | "http-errors": "2.0.0",
643 | "iconv-lite": "0.4.24",
644 | "unpipe": "1.0.0"
645 | },
646 | "engines": {
647 | "node": ">= 0.8"
648 | }
649 | },
650 | "node_modules/safe-buffer": {
651 | "version": "5.2.1",
652 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
653 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
654 | "funding": [
655 | {
656 | "type": "github",
657 | "url": "https://github.com/sponsors/feross"
658 | },
659 | {
660 | "type": "patreon",
661 | "url": "https://www.patreon.com/feross"
662 | },
663 | {
664 | "type": "consulting",
665 | "url": "https://feross.org/support"
666 | }
667 | ]
668 | },
669 | "node_modules/safer-buffer": {
670 | "version": "2.1.2",
671 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
672 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
673 | },
674 | "node_modules/send": {
675 | "version": "0.18.0",
676 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
677 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
678 | "dependencies": {
679 | "debug": "2.6.9",
680 | "depd": "2.0.0",
681 | "destroy": "1.2.0",
682 | "encodeurl": "~1.0.2",
683 | "escape-html": "~1.0.3",
684 | "etag": "~1.8.1",
685 | "fresh": "0.5.2",
686 | "http-errors": "2.0.0",
687 | "mime": "1.6.0",
688 | "ms": "2.1.3",
689 | "on-finished": "2.4.1",
690 | "range-parser": "~1.2.1",
691 | "statuses": "2.0.1"
692 | },
693 | "engines": {
694 | "node": ">= 0.8.0"
695 | }
696 | },
697 | "node_modules/send/node_modules/ms": {
698 | "version": "2.1.3",
699 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
700 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
701 | },
702 | "node_modules/seq-queue": {
703 | "version": "0.0.5",
704 | "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
705 | "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
706 | },
707 | "node_modules/serve-static": {
708 | "version": "1.15.0",
709 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
710 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
711 | "dependencies": {
712 | "encodeurl": "~1.0.2",
713 | "escape-html": "~1.0.3",
714 | "parseurl": "~1.3.3",
715 | "send": "0.18.0"
716 | },
717 | "engines": {
718 | "node": ">= 0.8.0"
719 | }
720 | },
721 | "node_modules/set-function-length": {
722 | "version": "1.2.2",
723 | "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
724 | "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
725 | "dependencies": {
726 | "define-data-property": "^1.1.4",
727 | "es-errors": "^1.3.0",
728 | "function-bind": "^1.1.2",
729 | "get-intrinsic": "^1.2.4",
730 | "gopd": "^1.0.1",
731 | "has-property-descriptors": "^1.0.2"
732 | },
733 | "engines": {
734 | "node": ">= 0.4"
735 | }
736 | },
737 | "node_modules/setprototypeof": {
738 | "version": "1.2.0",
739 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
740 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
741 | },
742 | "node_modules/side-channel": {
743 | "version": "1.0.6",
744 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
745 | "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
746 | "dependencies": {
747 | "call-bind": "^1.0.7",
748 | "es-errors": "^1.3.0",
749 | "get-intrinsic": "^1.2.4",
750 | "object-inspect": "^1.13.1"
751 | },
752 | "engines": {
753 | "node": ">= 0.4"
754 | },
755 | "funding": {
756 | "url": "https://github.com/sponsors/ljharb"
757 | }
758 | },
759 | "node_modules/statuses": {
760 | "version": "2.0.1",
761 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
762 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
763 | "engines": {
764 | "node": ">= 0.8"
765 | }
766 | },
767 | "node_modules/toidentifier": {
768 | "version": "1.0.1",
769 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
770 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
771 | "engines": {
772 | "node": ">=0.6"
773 | }
774 | },
775 | "node_modules/type-is": {
776 | "version": "1.6.18",
777 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
778 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
779 | "dependencies": {
780 | "media-typer": "0.3.0",
781 | "mime-types": "~2.1.24"
782 | },
783 | "engines": {
784 | "node": ">= 0.6"
785 | }
786 | },
787 | "node_modules/unpipe": {
788 | "version": "1.0.0",
789 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
790 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
791 | "engines": {
792 | "node": ">= 0.8"
793 | }
794 | },
795 | "node_modules/utils-merge": {
796 | "version": "1.0.1",
797 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
798 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
799 | "engines": {
800 | "node": ">= 0.4.0"
801 | }
802 | },
803 | "node_modules/vary": {
804 | "version": "1.1.2",
805 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
806 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
807 | "engines": {
808 | "node": ">= 0.8"
809 | }
810 | }
811 | }
812 | }
813 |
--------------------------------------------------------------------------------