├── README.md ├── backend ├── .gitignore ├── app.js ├── configs │ └── db.js ├── controllers │ ├── AuthorsController.js │ └── BooksController.js ├── db.sql ├── package-lock.json ├── package.json ├── routes │ └── index.js └── server.js └── frontend ├── .eslintrc.cjs ├── .gitignore ├── README.md ├── db.json ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── ss │ ├── authors.png │ ├── books.png │ └── dashboard.png └── vite.svg ├── src ├── App.css ├── App.tsx ├── AuthorsPage.tsx ├── BooksPage.tsx ├── ErrorPage.tsx ├── assets │ └── react.svg ├── components │ ├── AddEditAuthorModal.tsx │ ├── AddEditBookModal.tsx │ ├── AuthorCreateEditForm.tsx │ ├── BookCreateEditForm.tsx │ ├── DeleteAuthorModal.tsx │ ├── DeleteBookModal.tsx │ ├── IconDelete.tsx │ ├── IconEdit.tsx │ ├── IconView.tsx │ ├── ViewAuthorModal.tsx │ └── ViewBookModal.tsx ├── index.css ├── main.tsx ├── models │ ├── Author.ts │ └── Books.ts └── vite-env.d.ts ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /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/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | node_modules/ 4 | build/ 5 | tmp/ 6 | temp/ 7 | .DS_Store 8 | dist/* -------------------------------------------------------------------------------- /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; -------------------------------------------------------------------------------- /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; -------------------------------------------------------------------------------- /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(); -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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; -------------------------------------------------------------------------------- /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/.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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 |Sorry, an unexpected error has occurred.
11 |12 | {error.statusText || error.message} 13 |
14 |Are you sure, you want to delete author {` ${author?.name}`}?
24 |Are you sure, you want to delete book {` ${book?.title}`}?
24 |{ValueMappings[key as keyof typeof ValueMappings]}
30 |{author[key as keyof Author]}
31 |{ValueMappings[key as keyof typeof ValueMappings]}
34 |{book[key as keyof BookDTO]}
35 |