├── .gitignore ├── .husky └── pre-commit ├── CODE_OF_CONDUCT.MD ├── CONTRIBUTING.md ├── LICENSE.md ├── Procfile ├── README.md ├── asset ├── Picture1.png ├── Picture2.png └── Picture3.png ├── package-lock.json ├── package.json ├── requirements.txt └── src ├── app.js ├── controllers ├── auth.js ├── error.js ├── notifications.js ├── readings.js ├── search.js ├── subscriptions.js ├── tags.js └── users.js ├── helpers ├── auth.js └── notifications.js ├── middleware └── auth.js ├── models ├── index.js ├── reading.js ├── subscription.js ├── tags.js └── user.js ├── mysql ├── import.sql └── tables.sql ├── queries ├── reading.js ├── subscription.js ├── tag.js └── user.js ├── reading_scraper.py ├── reading_summary.py └── routes ├── auth.js ├── cspReport.js ├── readings.js ├── search.js ├── tags.js └── users.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | /node_modules 6 | /.pnp 7 | .pnp.js 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | .env 22 | /__pycache__ 23 | 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.MD: -------------------------------------------------------------------------------- 1 | ![](https://media.discordapp.net/attachments/635782799017771008/708029710814412871/ZTMCC.png) 2 | 3 | ## Introduction 4 | The Zero To Mastery Community is dedicated to providing a safe, inclusive, welcoming, and harassment-free space and experience for all community participants, regardless of gender identity and expression, sexual orientation, disability, physical appearance, socioeconomic status, body size, ethnicity, nationality, level of experience, age, religion (or lack thereof), or other identity markers. Our Code of Conduct exists because of that dedication, and we do not tolerate harassment in any form. 5 | 6 | If you cannot be bothered to read this whole thing, here is a nice and short summary: **Be a good human, be nice, and help those that need help. This group is about learning and for improving our skills as a community in order to better ourselves. If that does not fit into what you want to talk about, there are better places on the internet for you to have those discussions.** 7 | 8 | This Code of Conduct applies to __**EVERYONE**__ in **ALL** ZTM Community spaces, including: 9 | - ZTM Discord Server 10 | - ZTM open source repositories 11 | - ZTM Community events and meetups, online and offline 12 | - one-on-one communications pertaining to ZTM Community business. 13 | 14 | Participants violating our Code of Conduct may be penalized or expelled at the discretion of community management. 15 | 16 | Some ZTM Community spaces may have additional rules in place, which are posted publicly for participants. Participants are responsible for knowing and abiding by these rules. We invite all those who participate in the ZTM Community to help us create safe and positive community experiences. 17 | 18 | Consequences for noncompliance with the Code of Conduct may include a discussion with moderators, mediation with the participant you may have harassed, or as an absolute last resort, a ban from the community. 19 | 20 | ## Behaviour 21 | #### Appropriate behavior contributes to the health, safety, and longevity of the ZTM Community and includes: 22 | - Participating in an authentic and empathetic way. 23 | - Representing the ZTM Community in a positive, professional way. 24 | - Using welcoming and inclusive language. 25 | - Exercising consideration and respect in your speech and actions. 26 | - Refraining from demeaning, discriminatory, or harassing behavior and speech. 27 | - Being mindful of your surroundings and of your fellow participants. 28 | - Considering what is best for the community. 29 | - Alerting community moderators if you notice a dangerous situation, someone in distress, or unresolved violations of this Code of Conduct. 30 | - Refraining from doing something you wouldn’t do in another professional situation. 31 | - Remembering that community event venues may be shared with members of the public; being respectful to all patrons of these locations. 32 | - Keeping an open and curious mind without making assumptions about others. 33 | - Attempting collaboration before conflict. 34 | - Gracefully accepting well-communicated constructive criticism. 35 | 36 | #### Harassment or inappropriate behaviour includes: 37 | - Spamming similar content in multiple channels and/or the posting of affiliate links. 38 | - Community/Product/Affiliate advertising and promotion is prohibited unless approved by a member of the Management Team. 39 | - Discriminatory language and actions that reinforce social structures of domination related to gender, gender identity and expression, sexual orientation, disability, mental illness, neuro(a)typicality, physical appearance, body size, age, race, religion (or lack thereof), or other identity markers. 40 | - Excessive trolling or sustained, uninvited disruption. 41 | - Insults or personal attacks. 42 | - Violent or personally objectifying material. 43 | - Gratuitous or off-topic sexual images or behavior in spaces where they’re not appropriate. 44 | - Unwelcome sexual attention. 45 | - Violent language or threats of violence. 46 | - Incitement of violence towards any individual, including encouraging a person to commit suicide or to engage in self-harm. 47 | - Unwelcome comments regarding a person’s lifestyle choices and practices, including those related to food, health, parenting, drugs, and employment. 48 | - Deliberate misgendering or use of “dead” or rejected names. 49 | - Pattern of inappropriate social contact, such as requesting/assuming unprofessional levels of intimacy with others. 50 | - Continued one-on-one communication after requests to cease. 51 | - Deliberate “outing” of any aspect of a person’s identity without their consent. 52 | - Threatening to post or posting other people’s personally identifiable information without consent. 53 | - Publication of non-harassing private communication without consent. 54 | - Blogging, tweeting, or otherwise communicating with intent to harm someone’s reputation, i.e., “making an example” of a community participant. 55 | - Intimidation, stalking, or following. 56 | - Harassing or unwanted photography or recording, including logging online activity for harassment purposes. 57 | - Other conduct which could reasonably be considered inappropriate in a professional setting. 58 | - Advocating for, or encouraging, any of the above behavior. 59 | 60 | ## Reporting Procedure 61 | If you experience or witness (or have experienced or have witnessed) violations of the Code of Conduct or have any other concerns, please notify a member of the `Management Team` or `Star Mentors` on Discord, Reports of Code of Conduct violations should include as many details as possible, for example: 62 | - Discord handles of people involved 63 | - When and which channel(s) it happened 64 | - Detailed summary of what happened 65 | - Additional context / screenshots 66 | 67 | ## Enforcement 68 | - Participants will be asked to stop any harassing behavior are expected to comply immediately, even if participants do not agree with or fully acknowledge the behavior being reported. 69 | 70 | - If a participant violates the Code of Conduct, community moderators may take any action they deem appropriate to maintaining a welcoming environment for all participants, up to and including removing the participants messages, permissions or membership from the community. 71 | 72 | ## Moderators 73 | Code of Conduct moderators will: 74 | - Behind the scenes there will always be at least two people working together to resolve violations. 75 | - Respond as promptly as possible to reports of violations. 76 | - Make an effort to understand both sides of the situation. 77 | - Keep each other accountable. 78 | - Recuse themselves or be excused from handling an incident if they are listed as a possible Code of Conduct violator. 79 | 80 | 81 | 82 | ## Sources 83 | This code of conduct was adapted from [here](https://github.com/keen/community-code-of-conduct/blob/master/long-form-code-of-conduct.md) 84 | 85 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Breads Server 2 | 3 | We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's: 4 | 5 | - Reporting a bug 6 | - Discussing the current state of the code 7 | - Submitting a fix 8 | - Proposing new features 9 | - Becoming a maintainer 10 | 11 | ## Before you contribute 12 | 13 | Our aim is to **keep it simple** for the developers to contribute to this project. See the folder structure (with concise description) 14 | 15 | ## How to contribute 16 | 17 | 1. First, fork (make a copy) of this repo to your Github account. 18 | 19 | 2. Clone (download) your fork to your computer 20 | 21 | 3. Keep your clone in sync with the original repo (get the latest updates) 22 | 23 | HTTPS 24 | ``` 25 | git remote add upstream https://github.com/zero-to-mastery/breads-server.git 26 | git pull upstream master 27 | ``` 28 | 29 | SSH 30 | ``` 31 | git remote add upstream git@github.com:zero-to-mastery/breads-server.git 32 | git pull upstream master 33 | ``` 34 | ([For more info](https://www.freecodecamp.org/news/how-to-sync-your-fork-with-the-original-git-repository/)) 35 | 36 | 4. Download and install the latest version of [MySQL](https://dev.mysql.com/downloads/mysql/) 37 | **on macOS, run the following command to be able to use the `mysql` cli tool 38 | `export PATH=${PATH}:/usr/local/mysql/bin/` 39 | 40 | 5. Create your local database 41 | 42 | `Mysql -u root -p` and enter password you set during installation 43 | `CREATE DATABASE breads` 44 | 45 | 6. Create database tables and import seed data 46 | 47 | - Run `mysql -u -p < mysql/tables.sql` to create tables 48 | - Run `mysql -u -p < mysql/import.sql` to import seed data 49 | 50 | 7. Install Node.js and Python packages 51 | 52 | - Run `npm install` 53 | - Run `python3 -m pip install cython` 54 | - Run `python3 -m pip install -r requirements.txt` 55 | 56 | 8. Set up your local environment variables 57 | 58 | Create a `.env` file in the root directory: 59 | 60 | - `LOCAL_CORS` - frontend url (i.e. 'http://localhost:3000') 61 | 62 | **To make requests from tools like Postman, set `LOCAL_CORS=localhost:8080` (8080 is the default port) 63 | 64 | **MySQL** 65 | 66 | - `LOCAL_HOST` - local MySQL hostname 67 | - `LOCAL_USER` - local MySQL username 68 | - `LOCAL_DBPASSWORD` - local MySQL password 69 | - `LOCAL_DB` - local MySQL database name 70 | 71 | **JWT** - create a secret key for JWT based authentication 72 | 73 | - `SECRET_KEY` - JWT secret key 74 | 75 | **[Cloudinary](https://cloudinary.com/)** - Used for image hosting. Set up a free account to get a cloud name, API key, and API Secret _(only needed if working on user CRUD)_ 76 | 77 | - `CLOUDINARY_CLOUD_NAME` - Cloud Name 78 | - `CLOUDINARY_API_KEY` - API Key 79 | - `CLOUDINARY_API_SECRET` - API Secret 80 | 81 | **[Link Preview](https://www.linkpreview.net/)** - Used as a fallback for the webscraper. Set up a free account to get an API key _(only needed if working on webscraper)_ 82 | 83 | - `LINK_PREVIEW_KEY` - API Key 84 | 85 | **Nodemailer** - Used to send password reset emails. Add an email login information _(only needed if working on reset password feature)_ 86 | 87 | - `EMAIL_LOGIN` - email address 88 | - `EMAIL_PASSWORD` - email password 89 | - `EMAIL_URL` - frontend url (i.e 'http://localhost:3000') 90 | 91 | **Chrome Webdriver** - Used as a fallback to get reading data when the initial scrape fails. [Instructions to download here.](https://splinter.readthedocs.io/en/latest/drivers/chrome.html) _(only needed if working on webscraper)_ 92 | 93 | - `CHROMEDRIVER_DIR` - Place in the directory of the script and then copy the relative path and paste it here. If it's in a different directory, you'll need the complete path. 94 | 95 | At the end `.env` file will look like this: 96 | 97 | ``` 98 | LOCAL_CORS= 99 | LOCAL_HOST=localhost 100 | LOCAL_USER= 101 | LOCAL_DBPASSWORD= 102 | LOCAL_DB= 103 | CLOUDINARY_CLOUD_NAME= 104 | CLOUDINARY_API_KEY= 105 | CLOUDINARY_API_SECRET= 106 | SECRET_KEY= 107 | LINK_PREVIEW_KEY= 108 | CHROMEDRIVER_DIR= 109 | ``` 110 | 9. Run `npm start` and confirm the server is running 111 | 112 | 10. Create a new branch `git checkout -b `. 113 | 114 | 11. Start making your changes. 115 | 116 | 12. Pull from the upstream again before you commit your changes, like you did in step 3. This is to ensure your still have the latest code. 117 | 118 | If you see an error similar to `Your local changes to the following files would be overwritten by merge. Please commit your changes or stash them before you merge` on using `git pull upstream master` use: 119 | 120 | ``` 121 | git stash 122 | git pull upstream master 123 | git stash pop 124 | ``` 125 | 126 | ([For more info](https://bluecast.tech/blog/git-stash/)) 127 | 128 | 13. Commit and push the code to your fork 129 | 130 | 14. In your repo GitHub page, create a pull request to the `development` branch. This will allow us to see changes in a staging environment before merging to `master`. If everything runs correctly, your pull request will be merged into `master`. 131 | 132 | ([For more info](https://www.atlassian.com/git/tutorials/comparing-workflows/forking-workflow)) 133 | 134 | ## This project uses the Creative Commons Attribution 4.0 International License 135 | 136 | When you submit code changes, your submissions are understood to be under the same CC License that covers the project. Feel free to contact the maintainers if that's a concern. 137 | 138 | ## Report bugs using Github's [issues](../../issues) 139 | 140 | We use GitHub issues to track public bugs. Report a bug by [opening a new issue](../../issues); it's that easy! 141 | 142 | ## Write bug reports with detail, background, and sample code 143 | 144 | **Great Bug Reports** tend to have: 145 | 146 | - A quick summary and/or background 147 | - Steps to reproduce 148 | - Be specific! 149 | - Give sample code if you can. 150 | - What you expected would happen 151 | - What actually happens 152 | - Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) 153 | 154 | People _love_ thorough bug reports. I'm not even kidding. 155 | 156 | ## Use a Consistent Coding Style 157 | 158 | Observe the coding style of the project and add your code also in the same style. 159 | **Don't make major changes** (Like changing the complete folder structure) 160 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Breads (c) 2020 Austin Bundy 2 | 3 | Breads is licensed under a 4 | Creative Commons Attribution 4.0 International License. 5 | 6 | You should have received a copy of the license along with this 7 | work. If not, see . 8 | 9 | Attribution 4.0 International 10 | 11 | ======================================================================= 12 | 13 | Creative Commons Corporation ("Creative Commons") is not a law firm and 14 | does not provide legal services or legal advice. Distribution of 15 | Creative Commons public licenses does not create a lawyer-client or 16 | other relationship. Creative Commons makes its licenses and related 17 | information available on an "as-is" basis. Creative Commons gives no 18 | warranties regarding its licenses, any material licensed under their 19 | terms and conditions, or any related information. Creative Commons 20 | disclaims all liability for damages resulting from their use to the 21 | fullest extent possible. 22 | 23 | Using Creative Commons Public Licenses 24 | 25 | Creative Commons public licenses provide a standard set of terms and 26 | conditions that creators and other rights holders may use to share 27 | original works of authorship and other material subject to copyright 28 | and certain other rights specified in the public license below. The 29 | following considerations are for informational purposes only, are not 30 | exhaustive, and do not form part of our licenses. 31 | 32 | Considerations for licensors: Our public licenses are 33 | intended for use by those authorized to give the public 34 | permission to use material in ways otherwise restricted by 35 | copyright and certain other rights. Our licenses are 36 | irrevocable. Licensors should read and understand the terms 37 | and conditions of the license they choose before applying it. 38 | Licensors should also secure all rights necessary before 39 | applying our licenses so that the public can reuse the 40 | material as expected. Licensors should clearly mark any 41 | material not subject to the license. This includes other CC- 42 | licensed material, or material used under an exception or 43 | limitation to copyright. More considerations for licensors: 44 | wiki.creativecommons.org/Considerations_for_licensors 45 | 46 | Considerations for the public: By using one of our public 47 | licenses, a licensor grants the public permission to use the 48 | licensed material under specified terms and conditions. If 49 | the licensor's permission is not necessary for any reason--for 50 | example, because of any applicable exception or limitation to 51 | copyright--then that use is not regulated by the license. Our 52 | licenses grant only permissions under copyright and certain 53 | other rights that a licensor has authority to grant. Use of 54 | the licensed material may still be restricted for other 55 | reasons, including because others have copyright or other 56 | rights in the material. A licensor may make special requests, 57 | such as asking that all changes be marked or described. 58 | Although not required by our licenses, you are encouraged to 59 | respect those requests where reasonable. More_considerations 60 | for the public: 61 | wiki.creativecommons.org/Considerations_for_licensees 62 | 63 | ======================================================================= 64 | 65 | Creative Commons Attribution 4.0 International Public License 66 | 67 | By exercising the Licensed Rights (defined below), You accept and agree 68 | to be bound by the terms and conditions of this Creative Commons 69 | Attribution 4.0 International Public License ("Public License"). To the 70 | extent this Public License may be interpreted as a contract, You are 71 | granted the Licensed Rights in consideration of Your acceptance of 72 | these terms and conditions, and the Licensor grants You such rights in 73 | consideration of benefits the Licensor receives from making the 74 | Licensed Material available under these terms and conditions. 75 | 76 | 77 | Section 1 -- Definitions. 78 | 79 | a. Adapted Material means material subject to Copyright and Similar 80 | Rights that is derived from or based upon the Licensed Material 81 | and in which the Licensed Material is translated, altered, 82 | arranged, transformed, or otherwise modified in a manner requiring 83 | permission under the Copyright and Similar Rights held by the 84 | Licensor. For purposes of this Public License, where the Licensed 85 | Material is a musical work, performance, or sound recording, 86 | Adapted Material is always produced where the Licensed Material is 87 | synched in timed relation with a moving image. 88 | 89 | b. Adapter's License means the license You apply to Your Copyright 90 | and Similar Rights in Your contributions to Adapted Material in 91 | accordance with the terms and conditions of this Public License. 92 | 93 | c. Copyright and Similar Rights means copyright and/or similar rights 94 | closely related to copyright including, without limitation, 95 | performance, broadcast, sound recording, and Sui Generis Database 96 | Rights, without regard to how the rights are labeled or 97 | categorized. For purposes of this Public License, the rights 98 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 99 | Rights. 100 | 101 | d. Effective Technological Measures means those measures that, in the 102 | absence of proper authority, may not be circumvented under laws 103 | fulfilling obligations under Article 11 of the WIPO Copyright 104 | Treaty adopted on December 20, 1996, and/or similar international 105 | agreements. 106 | 107 | e. Exceptions and Limitations means fair use, fair dealing, and/or 108 | any other exception or limitation to Copyright and Similar Rights 109 | that applies to Your use of the Licensed Material. 110 | 111 | f. Licensed Material means the artistic or literary work, database, 112 | or other material to which the Licensor applied this Public 113 | License. 114 | 115 | g. Licensed Rights means the rights granted to You subject to the 116 | terms and conditions of this Public License, which are limited to 117 | all Copyright and Similar Rights that apply to Your use of the 118 | Licensed Material and that the Licensor has authority to license. 119 | 120 | h. Licensor means the individual(s) or entity(ies) granting rights 121 | under this Public License. 122 | 123 | i. Share means to provide material to the public by any means or 124 | process that requires permission under the Licensed Rights, such 125 | as reproduction, public display, public performance, distribution, 126 | dissemination, communication, or importation, and to make material 127 | available to the public including in ways that members of the 128 | public may access the material from a place and at a time 129 | individually chosen by them. 130 | 131 | j. Sui Generis Database Rights means rights other than copyright 132 | resulting from Directive 96/9/EC of the European Parliament and of 133 | the Council of 11 March 1996 on the legal protection of databases, 134 | as amended and/or succeeded, as well as other essentially 135 | equivalent rights anywhere in the world. 136 | 137 | k. You means the individual or entity exercising the Licensed Rights 138 | under this Public License. Your has a corresponding meaning. 139 | 140 | 141 | Section 2 -- Scope. 142 | 143 | a. License grant. 144 | 145 | 1. Subject to the terms and conditions of this Public License, 146 | the Licensor hereby grants You a worldwide, royalty-free, 147 | non-sublicensable, non-exclusive, irrevocable license to 148 | exercise the Licensed Rights in the Licensed Material to: 149 | 150 | a. reproduce and Share the Licensed Material, in whole or 151 | in part; and 152 | 153 | b. produce, reproduce, and Share Adapted Material. 154 | 155 | 2. Exceptions and Limitations. For the avoidance of doubt, where 156 | Exceptions and Limitations apply to Your use, this Public 157 | License does not apply, and You do not need to comply with 158 | its terms and conditions. 159 | 160 | 3. Term. The term of this Public License is specified in Section 161 | 6(a). 162 | 163 | 4. Media and formats; technical modifications allowed. The 164 | Licensor authorizes You to exercise the Licensed Rights in 165 | all media and formats whether now known or hereafter created, 166 | and to make technical modifications necessary to do so. The 167 | Licensor waives and/or agrees not to assert any right or 168 | authority to forbid You from making technical modifications 169 | necessary to exercise the Licensed Rights, including 170 | technical modifications necessary to circumvent Effective 171 | Technological Measures. For purposes of this Public License, 172 | simply making modifications authorized by this Section 2(a) 173 | (4) never produces Adapted Material. 174 | 175 | 5. Downstream recipients. 176 | 177 | a. Offer from the Licensor -- Licensed Material. Every 178 | recipient of the Licensed Material automatically 179 | receives an offer from the Licensor to exercise the 180 | Licensed Rights under the terms and conditions of this 181 | Public License. 182 | 183 | b. No downstream restrictions. You may not offer or impose 184 | any additional or different terms or conditions on, or 185 | apply any Effective Technological Measures to, the 186 | Licensed Material if doing so restricts exercise of the 187 | Licensed Rights by any recipient of the Licensed 188 | Material. 189 | 190 | 6. No endorsement. Nothing in this Public License constitutes or 191 | may be construed as permission to assert or imply that You 192 | are, or that Your use of the Licensed Material is, connected 193 | with, or sponsored, endorsed, or granted official status by, 194 | the Licensor or others designated to receive attribution as 195 | provided in Section 3(a)(1)(A)(i). 196 | 197 | b. Other rights. 198 | 199 | 1. Moral rights, such as the right of integrity, are not 200 | licensed under this Public License, nor are publicity, 201 | privacy, and/or other similar personality rights; however, to 202 | the extent possible, the Licensor waives and/or agrees not to 203 | assert any such rights held by the Licensor to the limited 204 | extent necessary to allow You to exercise the Licensed 205 | Rights, but not otherwise. 206 | 207 | 2. Patent and trademark rights are not licensed under this 208 | Public License. 209 | 210 | 3. To the extent possible, the Licensor waives any right to 211 | collect royalties from You for the exercise of the Licensed 212 | Rights, whether directly or through a collecting society 213 | under any voluntary or waivable statutory or compulsory 214 | licensing scheme. In all other cases the Licensor expressly 215 | reserves any right to collect such royalties. 216 | 217 | 218 | Section 3 -- License Conditions. 219 | 220 | Your exercise of the Licensed Rights is expressly made subject to the 221 | following conditions. 222 | 223 | a. Attribution. 224 | 225 | 1. If You Share the Licensed Material (including in modified 226 | form), You must: 227 | 228 | a. retain the following if it is supplied by the Licensor 229 | with the Licensed Material: 230 | 231 | i. identification of the creator(s) of the Licensed 232 | Material and any others designated to receive 233 | attribution, in any reasonable manner requested by 234 | the Licensor (including by pseudonym if 235 | designated); 236 | 237 | ii. a copyright notice; 238 | 239 | iii. a notice that refers to this Public License; 240 | 241 | iv. a notice that refers to the disclaimer of 242 | warranties; 243 | 244 | v. a URI or hyperlink to the Licensed Material to the 245 | extent reasonably practicable; 246 | 247 | b. indicate if You modified the Licensed Material and 248 | retain an indication of any previous modifications; and 249 | 250 | c. indicate the Licensed Material is licensed under this 251 | Public License, and include the text of, or the URI or 252 | hyperlink to, this Public License. 253 | 254 | 2. You may satisfy the conditions in Section 3(a)(1) in any 255 | reasonable manner based on the medium, means, and context in 256 | which You Share the Licensed Material. For example, it may be 257 | reasonable to satisfy the conditions by providing a URI or 258 | hyperlink to a resource that includes the required 259 | information. 260 | 261 | 3. If requested by the Licensor, You must remove any of the 262 | information required by Section 3(a)(1)(A) to the extent 263 | reasonably practicable. 264 | 265 | 4. If You Share Adapted Material You produce, the Adapter's 266 | License You apply must not prevent recipients of the Adapted 267 | Material from complying with this Public License. 268 | 269 | 270 | Section 4 -- Sui Generis Database Rights. 271 | 272 | Where the Licensed Rights include Sui Generis Database Rights that 273 | apply to Your use of the Licensed Material: 274 | 275 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 276 | to extract, reuse, reproduce, and Share all or a substantial 277 | portion of the contents of the database; 278 | 279 | b. if You include all or a substantial portion of the database 280 | contents in a database in which You have Sui Generis Database 281 | Rights, then the database in which You have Sui Generis Database 282 | Rights (but not its individual contents) is Adapted Material; and 283 | 284 | c. You must comply with the conditions in Section 3(a) if You Share 285 | all or a substantial portion of the contents of the database. 286 | 287 | For the avoidance of doubt, this Section 4 supplements and does not 288 | replace Your obligations under this Public License where the Licensed 289 | Rights include other Copyright and Similar Rights. 290 | 291 | 292 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 293 | 294 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 295 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 296 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 297 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 298 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 299 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 300 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 301 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 302 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 303 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 304 | 305 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 306 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 307 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 308 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 309 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 310 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 311 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 312 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 313 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 314 | 315 | c. The disclaimer of warranties and limitation of liability provided 316 | above shall be interpreted in a manner that, to the extent 317 | possible, most closely approximates an absolute disclaimer and 318 | waiver of all liability. 319 | 320 | 321 | Section 6 -- Term and Termination. 322 | 323 | a. This Public License applies for the term of the Copyright and 324 | Similar Rights licensed here. However, if You fail to comply with 325 | this Public License, then Your rights under this Public License 326 | terminate automatically. 327 | 328 | b. Where Your right to use the Licensed Material has terminated under 329 | Section 6(a), it reinstates: 330 | 331 | 1. automatically as of the date the violation is cured, provided 332 | it is cured within 30 days of Your discovery of the 333 | violation; or 334 | 335 | 2. upon express reinstatement by the Licensor. 336 | 337 | For the avoidance of doubt, this Section 6(b) does not affect any 338 | right the Licensor may have to seek remedies for Your violations 339 | of this Public License. 340 | 341 | c. For the avoidance of doubt, the Licensor may also offer the 342 | Licensed Material under separate terms or conditions or stop 343 | distributing the Licensed Material at any time; however, doing so 344 | will not terminate this Public License. 345 | 346 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 347 | License. 348 | 349 | 350 | Section 7 -- Other Terms and Conditions. 351 | 352 | a. The Licensor shall not be bound by any additional or different 353 | terms or conditions communicated by You unless expressly agreed. 354 | 355 | b. Any arrangements, understandings, or agreements regarding the 356 | Licensed Material not stated herein are separate from and 357 | independent of the terms and conditions of this Public License. 358 | 359 | 360 | Section 8 -- Interpretation. 361 | 362 | a. For the avoidance of doubt, this Public License does not, and 363 | shall not be interpreted to, reduce, limit, restrict, or impose 364 | conditions on any use of the Licensed Material that could lawfully 365 | be made without permission under this Public License. 366 | 367 | b. To the extent possible, if any provision of this Public License is 368 | deemed unenforceable, it shall be automatically reformed to the 369 | minimum extent necessary to make it enforceable. If the provision 370 | cannot be reformed, it shall be severed from this Public License 371 | without affecting the enforceability of the remaining terms and 372 | conditions. 373 | 374 | c. No term or condition of this Public License will be waived and no 375 | failure to comply consented to unless expressly agreed to by the 376 | Licensor. 377 | 378 | d. Nothing in this Public License constitutes or may be interpreted 379 | as a limitation upon, or waiver of, any privileges and immunities 380 | that apply to the Licensor or You, including from the legal 381 | processes of any jurisdiction or authority. 382 | 383 | 384 | ======================================================================= 385 | 386 | Creative Commons is not a party to its public 387 | licenses. Notwithstanding, Creative Commons may elect to apply one of 388 | its public licenses to material it publishes and in those instances 389 | will be considered the “Licensor.” The text of the Creative Commons 390 | public licenses is dedicated to the public domain under the CC0 Public 391 | Domain Dedication. Except for the limited purpose of indicating that 392 | material is shared under a Creative Commons public license or as 393 | otherwise permitted by the Creative Commons policies published at 394 | creativecommons.org/policies, Creative Commons does not authorize the 395 | use of the trademark "Creative Commons" or any other trademark or logo 396 | of Creative Commons without its prior written consent including, 397 | without limitation, in connection with any unauthorized modifications 398 | to any of its public licenses or any other arrangements, 399 | understandings, or agreements concerning use of licensed material. For 400 | the avoidance of doubt, this paragraph does not form part of the 401 | public licenses. 402 | 403 | Creative Commons may be contacted at creativecommons.org. 404 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node src/app.js -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Breads API 2 | 3 | An Express REST API service, Python web scraper, and MySQL database for [breads.io](https://www.breads.io/). See [breads-client](https://github.com/aTmb405/breads-client) for front end code and more details about the project. 4 | 5 | ## Table of Contents 6 | - [Contributing](#contributing) 7 | - [Technologies](#technologies) 8 | 9 | ## Contributing 10 | 11 | Please read through our [contributing guidelines](https://github.com/zero-to-mastery/breads-server/blob/master/CONTRIBUTING.md). Included are notes on development, directions for opening issues, coding standards, and a short explanation on the folder structure of the project. 12 | 13 | ## Technologies 14 | 15 | - [Node.js](https://nodejs.org/en/) 16 | - [Express](http://expressjs.com/) 17 | - [Python](https://www.python.org/) 18 | - [MySQL](https://www.mysql.com/) 19 | 20 | ### Near Future 21 | 22 | - [CircleCI](https://circleci.com/) 23 | - [Prettier](https://prettier.io/) 24 | - [ESLint](https://eslint.org/) 25 | -------------------------------------------------------------------------------- /asset/Picture1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zero-to-mastery/breads-server/6921769ec33bccded9fecee6438f9f80939236ea/asset/Picture1.png -------------------------------------------------------------------------------- /asset/Picture2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zero-to-mastery/breads-server/6921769ec33bccded9fecee6438f9f80939236ea/asset/Picture2.png -------------------------------------------------------------------------------- /asset/Picture3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zero-to-mastery/breads-server/6921769ec33bccded9fecee6438f9f80939236ea/asset/Picture3.png -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "breads", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/code-frame": { 8 | "version": "7.14.5", 9 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", 10 | "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", 11 | "dev": true, 12 | "requires": { 13 | "@babel/highlight": "^7.14.5" 14 | } 15 | }, 16 | "@babel/helper-validator-identifier": { 17 | "version": "7.14.9", 18 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", 19 | "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", 20 | "dev": true 21 | }, 22 | "@babel/highlight": { 23 | "version": "7.14.5", 24 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", 25 | "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", 26 | "dev": true, 27 | "requires": { 28 | "@babel/helper-validator-identifier": "^7.14.5", 29 | "chalk": "^2.0.0", 30 | "js-tokens": "^4.0.0" 31 | }, 32 | "dependencies": { 33 | "ansi-styles": { 34 | "version": "3.2.1", 35 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 36 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 37 | "dev": true, 38 | "requires": { 39 | "color-convert": "^1.9.0" 40 | } 41 | }, 42 | "chalk": { 43 | "version": "2.4.2", 44 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 45 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 46 | "dev": true, 47 | "requires": { 48 | "ansi-styles": "^3.2.1", 49 | "escape-string-regexp": "^1.0.5", 50 | "supports-color": "^5.3.0" 51 | } 52 | }, 53 | "color-convert": { 54 | "version": "1.9.3", 55 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 56 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 57 | "dev": true, 58 | "requires": { 59 | "color-name": "1.1.3" 60 | } 61 | }, 62 | "color-name": { 63 | "version": "1.1.3", 64 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 65 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 66 | "dev": true 67 | }, 68 | "has-flag": { 69 | "version": "3.0.0", 70 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 71 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 72 | "dev": true 73 | }, 74 | "supports-color": { 75 | "version": "5.5.0", 76 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 77 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 78 | "dev": true, 79 | "requires": { 80 | "has-flag": "^3.0.0" 81 | } 82 | } 83 | } 84 | }, 85 | "@types/parse-json": { 86 | "version": "4.0.0", 87 | "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", 88 | "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", 89 | "dev": true 90 | }, 91 | "abbrev": { 92 | "version": "1.1.1", 93 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 94 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" 95 | }, 96 | "accepts": { 97 | "version": "1.3.7", 98 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 99 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 100 | "requires": { 101 | "mime-types": "~2.1.24", 102 | "negotiator": "0.6.2" 103 | } 104 | }, 105 | "aggregate-error": { 106 | "version": "3.1.0", 107 | "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", 108 | "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", 109 | "dev": true, 110 | "requires": { 111 | "clean-stack": "^2.0.0", 112 | "indent-string": "^4.0.0" 113 | } 114 | }, 115 | "ansi-colors": { 116 | "version": "4.1.1", 117 | "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", 118 | "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", 119 | "dev": true 120 | }, 121 | "ansi-escapes": { 122 | "version": "4.3.2", 123 | "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", 124 | "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", 125 | "dev": true, 126 | "requires": { 127 | "type-fest": "^0.21.3" 128 | } 129 | }, 130 | "ansi-regex": { 131 | "version": "2.1.1", 132 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 133 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" 134 | }, 135 | "ansi-styles": { 136 | "version": "4.3.0", 137 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 138 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 139 | "dev": true, 140 | "requires": { 141 | "color-convert": "^2.0.1" 142 | } 143 | }, 144 | "append-field": { 145 | "version": "1.0.0", 146 | "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", 147 | "integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=" 148 | }, 149 | "aproba": { 150 | "version": "1.2.0", 151 | "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", 152 | "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" 153 | }, 154 | "are-we-there-yet": { 155 | "version": "1.1.5", 156 | "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", 157 | "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", 158 | "requires": { 159 | "delegates": "^1.0.0", 160 | "readable-stream": "^2.0.6" 161 | } 162 | }, 163 | "array-flatten": { 164 | "version": "1.1.1", 165 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 166 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 167 | }, 168 | "astral-regex": { 169 | "version": "2.0.0", 170 | "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", 171 | "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", 172 | "dev": true 173 | }, 174 | "balanced-match": { 175 | "version": "1.0.0", 176 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 177 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 178 | }, 179 | "basic-auth": { 180 | "version": "2.0.1", 181 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", 182 | "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", 183 | "requires": { 184 | "safe-buffer": "5.1.2" 185 | } 186 | }, 187 | "bcrypt": { 188 | "version": "5.0.0", 189 | "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.0.tgz", 190 | "integrity": "sha512-jB0yCBl4W/kVHM2whjfyqnxTmOHkCX4kHEa5nYKSoGeYe8YrjTYTc87/6bwt1g8cmV0QrbhKriETg9jWtcREhg==", 191 | "requires": { 192 | "node-addon-api": "^3.0.0", 193 | "node-pre-gyp": "0.15.0" 194 | } 195 | }, 196 | "bignumber.js": { 197 | "version": "9.0.0", 198 | "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", 199 | "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" 200 | }, 201 | "body-parser": { 202 | "version": "1.19.0", 203 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 204 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 205 | "requires": { 206 | "bytes": "3.1.0", 207 | "content-type": "~1.0.4", 208 | "debug": "2.6.9", 209 | "depd": "~1.1.2", 210 | "http-errors": "1.7.2", 211 | "iconv-lite": "0.4.24", 212 | "on-finished": "~2.3.0", 213 | "qs": "6.7.0", 214 | "raw-body": "2.4.0", 215 | "type-is": "~1.6.17" 216 | } 217 | }, 218 | "bowser": { 219 | "version": "2.9.0", 220 | "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.9.0.tgz", 221 | "integrity": "sha512-2ld76tuLBNFekRgmJfT2+3j5MIrP6bFict8WAIT3beq+srz1gcKNAdNKMqHqauQt63NmAa88HfP1/Ypa9Er3HA==" 222 | }, 223 | "brace-expansion": { 224 | "version": "1.1.11", 225 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 226 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 227 | "requires": { 228 | "balanced-match": "^1.0.0", 229 | "concat-map": "0.0.1" 230 | } 231 | }, 232 | "braces": { 233 | "version": "3.0.2", 234 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 235 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 236 | "dev": true, 237 | "requires": { 238 | "fill-range": "^7.0.1" 239 | } 240 | }, 241 | "buffer-equal-constant-time": { 242 | "version": "1.0.1", 243 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", 244 | "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" 245 | }, 246 | "buffer-from": { 247 | "version": "1.1.1", 248 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 249 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" 250 | }, 251 | "busboy": { 252 | "version": "0.2.14", 253 | "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", 254 | "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", 255 | "requires": { 256 | "dicer": "0.2.5", 257 | "readable-stream": "1.1.x" 258 | }, 259 | "dependencies": { 260 | "isarray": { 261 | "version": "0.0.1", 262 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 263 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" 264 | }, 265 | "readable-stream": { 266 | "version": "1.1.14", 267 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", 268 | "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", 269 | "requires": { 270 | "core-util-is": "~1.0.0", 271 | "inherits": "~2.0.1", 272 | "isarray": "0.0.1", 273 | "string_decoder": "~0.10.x" 274 | } 275 | }, 276 | "string_decoder": { 277 | "version": "0.10.31", 278 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", 279 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" 280 | } 281 | } 282 | }, 283 | "bytes": { 284 | "version": "3.1.0", 285 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 286 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 287 | }, 288 | "callsites": { 289 | "version": "3.1.0", 290 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 291 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 292 | "dev": true 293 | }, 294 | "camelize": { 295 | "version": "1.0.0", 296 | "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", 297 | "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" 298 | }, 299 | "chalk": { 300 | "version": "4.1.2", 301 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 302 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 303 | "dev": true, 304 | "requires": { 305 | "ansi-styles": "^4.1.0", 306 | "supports-color": "^7.1.0" 307 | } 308 | }, 309 | "chownr": { 310 | "version": "1.1.4", 311 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", 312 | "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" 313 | }, 314 | "clean-stack": { 315 | "version": "2.2.0", 316 | "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", 317 | "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", 318 | "dev": true 319 | }, 320 | "cli-cursor": { 321 | "version": "3.1.0", 322 | "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", 323 | "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", 324 | "dev": true, 325 | "requires": { 326 | "restore-cursor": "^3.1.0" 327 | } 328 | }, 329 | "cli-truncate": { 330 | "version": "2.1.0", 331 | "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", 332 | "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", 333 | "dev": true, 334 | "requires": { 335 | "slice-ansi": "^3.0.0", 336 | "string-width": "^4.2.0" 337 | }, 338 | "dependencies": { 339 | "ansi-regex": { 340 | "version": "5.0.0", 341 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", 342 | "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", 343 | "dev": true 344 | }, 345 | "is-fullwidth-code-point": { 346 | "version": "3.0.0", 347 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 348 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 349 | "dev": true 350 | }, 351 | "string-width": { 352 | "version": "4.2.2", 353 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", 354 | "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", 355 | "dev": true, 356 | "requires": { 357 | "emoji-regex": "^8.0.0", 358 | "is-fullwidth-code-point": "^3.0.0", 359 | "strip-ansi": "^6.0.0" 360 | } 361 | }, 362 | "strip-ansi": { 363 | "version": "6.0.0", 364 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", 365 | "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", 366 | "dev": true, 367 | "requires": { 368 | "ansi-regex": "^5.0.0" 369 | } 370 | } 371 | } 372 | }, 373 | "cloudinary": { 374 | "version": "1.23.0", 375 | "resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-1.23.0.tgz", 376 | "integrity": "sha512-akOxzroonvwWkuSVq7BI50nYpZPRXc5DbQIYETCVeKX9ZoToH2Gvc3MdUH63UtKiszuGYE51q2B+jQsJkBp2AQ==", 377 | "requires": { 378 | "cloudinary-core": "^2.10.2", 379 | "core-js": "3.6.5", 380 | "lodash": "^4.17.11", 381 | "q": "^1.5.1" 382 | } 383 | }, 384 | "cloudinary-core": { 385 | "version": "2.11.2", 386 | "resolved": "https://registry.npmjs.org/cloudinary-core/-/cloudinary-core-2.11.2.tgz", 387 | "integrity": "sha512-Dl545+AzPRGjx58cXB9gznXtA1dol0pmDqTxHos1hRcdbUcpUcuVzPkBnJjNO3F4K6BfZ5kSda9kzD+Qu7Yhgg==" 388 | }, 389 | "code-point-at": { 390 | "version": "1.1.0", 391 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 392 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" 393 | }, 394 | "color-convert": { 395 | "version": "2.0.1", 396 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 397 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 398 | "dev": true, 399 | "requires": { 400 | "color-name": "~1.1.4" 401 | } 402 | }, 403 | "color-name": { 404 | "version": "1.1.4", 405 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 406 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 407 | "dev": true 408 | }, 409 | "colorette": { 410 | "version": "1.2.2", 411 | "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", 412 | "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", 413 | "dev": true 414 | }, 415 | "commander": { 416 | "version": "7.2.0", 417 | "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", 418 | "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", 419 | "dev": true 420 | }, 421 | "compressible": { 422 | "version": "2.0.18", 423 | "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", 424 | "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", 425 | "requires": { 426 | "mime-db": ">= 1.43.0 < 2" 427 | } 428 | }, 429 | "compression": { 430 | "version": "1.7.4", 431 | "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", 432 | "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", 433 | "requires": { 434 | "accepts": "~1.3.5", 435 | "bytes": "3.0.0", 436 | "compressible": "~2.0.16", 437 | "debug": "2.6.9", 438 | "on-headers": "~1.0.2", 439 | "safe-buffer": "5.1.2", 440 | "vary": "~1.1.2" 441 | }, 442 | "dependencies": { 443 | "bytes": { 444 | "version": "3.0.0", 445 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", 446 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" 447 | } 448 | } 449 | }, 450 | "concat-map": { 451 | "version": "0.0.1", 452 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 453 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 454 | }, 455 | "concat-stream": { 456 | "version": "1.6.2", 457 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", 458 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", 459 | "requires": { 460 | "buffer-from": "^1.0.0", 461 | "inherits": "^2.0.3", 462 | "readable-stream": "^2.2.2", 463 | "typedarray": "^0.0.6" 464 | } 465 | }, 466 | "console-control-strings": { 467 | "version": "1.1.0", 468 | "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", 469 | "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" 470 | }, 471 | "content-disposition": { 472 | "version": "0.5.3", 473 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 474 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 475 | "requires": { 476 | "safe-buffer": "5.1.2" 477 | } 478 | }, 479 | "content-security-policy-builder": { 480 | "version": "2.1.0", 481 | "resolved": "https://registry.npmjs.org/content-security-policy-builder/-/content-security-policy-builder-2.1.0.tgz", 482 | "integrity": "sha512-/MtLWhJVvJNkA9dVLAp6fg9LxD2gfI6R2Fi1hPmfjYXSahJJzcfvoeDOxSyp4NvxMuwWv3WMssE9o31DoULHrQ==" 483 | }, 484 | "content-type": { 485 | "version": "1.0.4", 486 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 487 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 488 | }, 489 | "cookie": { 490 | "version": "0.4.0", 491 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 492 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 493 | }, 494 | "cookie-signature": { 495 | "version": "1.0.6", 496 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 497 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 498 | }, 499 | "core-js": { 500 | "version": "3.6.5", 501 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", 502 | "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" 503 | }, 504 | "core-util-is": { 505 | "version": "1.0.2", 506 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 507 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 508 | }, 509 | "cors": { 510 | "version": "2.8.5", 511 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 512 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 513 | "requires": { 514 | "object-assign": "^4", 515 | "vary": "^1" 516 | } 517 | }, 518 | "cosmiconfig": { 519 | "version": "7.0.0", 520 | "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", 521 | "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", 522 | "dev": true, 523 | "requires": { 524 | "@types/parse-json": "^4.0.0", 525 | "import-fresh": "^3.2.1", 526 | "parse-json": "^5.0.0", 527 | "path-type": "^4.0.0", 528 | "yaml": "^1.10.0" 529 | } 530 | }, 531 | "cross-spawn": { 532 | "version": "7.0.3", 533 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 534 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 535 | "dev": true, 536 | "requires": { 537 | "path-key": "^3.1.0", 538 | "shebang-command": "^2.0.0", 539 | "which": "^2.0.1" 540 | } 541 | }, 542 | "dasherize": { 543 | "version": "2.0.0", 544 | "resolved": "https://registry.npmjs.org/dasherize/-/dasherize-2.0.0.tgz", 545 | "integrity": "sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg=" 546 | }, 547 | "datauri": { 548 | "version": "2.0.0", 549 | "resolved": "https://registry.npmjs.org/datauri/-/datauri-2.0.0.tgz", 550 | "integrity": "sha512-zS2HSf9pI5XPlNZgIqJg/wCJpecgU/HA6E/uv2EfaWnW1EiTGLfy/EexTIsC9c99yoCOTXlqeeWk4FkCSuO3/g==", 551 | "requires": { 552 | "image-size": "^0.7.3", 553 | "mimer": "^1.0.0" 554 | } 555 | }, 556 | "debug": { 557 | "version": "2.6.9", 558 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 559 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 560 | "requires": { 561 | "ms": "2.0.0" 562 | } 563 | }, 564 | "deep-extend": { 565 | "version": "0.6.0", 566 | "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", 567 | "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" 568 | }, 569 | "delegates": { 570 | "version": "1.0.0", 571 | "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", 572 | "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" 573 | }, 574 | "denque": { 575 | "version": "1.5.0", 576 | "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz", 577 | "integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==" 578 | }, 579 | "depd": { 580 | "version": "1.1.2", 581 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 582 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 583 | }, 584 | "destroy": { 585 | "version": "1.0.4", 586 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 587 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 588 | }, 589 | "detect-libc": { 590 | "version": "1.0.3", 591 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", 592 | "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" 593 | }, 594 | "dicer": { 595 | "version": "0.2.5", 596 | "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", 597 | "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", 598 | "requires": { 599 | "readable-stream": "1.1.x", 600 | "streamsearch": "0.1.2" 601 | }, 602 | "dependencies": { 603 | "isarray": { 604 | "version": "0.0.1", 605 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 606 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" 607 | }, 608 | "readable-stream": { 609 | "version": "1.1.14", 610 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", 611 | "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", 612 | "requires": { 613 | "core-util-is": "~1.0.0", 614 | "inherits": "~2.0.1", 615 | "isarray": "0.0.1", 616 | "string_decoder": "~0.10.x" 617 | } 618 | }, 619 | "string_decoder": { 620 | "version": "0.10.31", 621 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", 622 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" 623 | } 624 | } 625 | }, 626 | "dont-sniff-mimetype": { 627 | "version": "1.1.0", 628 | "resolved": "https://registry.npmjs.org/dont-sniff-mimetype/-/dont-sniff-mimetype-1.1.0.tgz", 629 | "integrity": "sha512-ZjI4zqTaxveH2/tTlzS1wFp+7ncxNZaIEWYg3lzZRHkKf5zPT/MnEG6WL0BhHMJUabkh8GeU5NL5j+rEUCb7Ug==" 630 | }, 631 | "dotenv": { 632 | "version": "8.2.0", 633 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", 634 | "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" 635 | }, 636 | "ecdsa-sig-formatter": { 637 | "version": "1.0.11", 638 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", 639 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", 640 | "requires": { 641 | "safe-buffer": "^5.0.1" 642 | } 643 | }, 644 | "ee-first": { 645 | "version": "1.1.1", 646 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 647 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 648 | }, 649 | "emoji-regex": { 650 | "version": "8.0.0", 651 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 652 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 653 | "dev": true 654 | }, 655 | "encodeurl": { 656 | "version": "1.0.2", 657 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 658 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 659 | }, 660 | "enquirer": { 661 | "version": "2.3.6", 662 | "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", 663 | "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", 664 | "dev": true, 665 | "requires": { 666 | "ansi-colors": "^4.1.1" 667 | } 668 | }, 669 | "error-ex": { 670 | "version": "1.3.2", 671 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", 672 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", 673 | "dev": true, 674 | "requires": { 675 | "is-arrayish": "^0.2.1" 676 | } 677 | }, 678 | "escape-html": { 679 | "version": "1.0.3", 680 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 681 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 682 | }, 683 | "escape-string-regexp": { 684 | "version": "1.0.5", 685 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 686 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 687 | "dev": true 688 | }, 689 | "etag": { 690 | "version": "1.8.1", 691 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 692 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 693 | }, 694 | "execa": { 695 | "version": "5.1.1", 696 | "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", 697 | "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", 698 | "dev": true, 699 | "requires": { 700 | "cross-spawn": "^7.0.3", 701 | "get-stream": "^6.0.0", 702 | "human-signals": "^2.1.0", 703 | "is-stream": "^2.0.0", 704 | "merge-stream": "^2.0.0", 705 | "npm-run-path": "^4.0.1", 706 | "onetime": "^5.1.2", 707 | "signal-exit": "^3.0.3", 708 | "strip-final-newline": "^2.0.0" 709 | } 710 | }, 711 | "express": { 712 | "version": "4.17.1", 713 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 714 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 715 | "requires": { 716 | "accepts": "~1.3.7", 717 | "array-flatten": "1.1.1", 718 | "body-parser": "1.19.0", 719 | "content-disposition": "0.5.3", 720 | "content-type": "~1.0.4", 721 | "cookie": "0.4.0", 722 | "cookie-signature": "1.0.6", 723 | "debug": "2.6.9", 724 | "depd": "~1.1.2", 725 | "encodeurl": "~1.0.2", 726 | "escape-html": "~1.0.3", 727 | "etag": "~1.8.1", 728 | "finalhandler": "~1.1.2", 729 | "fresh": "0.5.2", 730 | "merge-descriptors": "1.0.1", 731 | "methods": "~1.1.2", 732 | "on-finished": "~2.3.0", 733 | "parseurl": "~1.3.3", 734 | "path-to-regexp": "0.1.7", 735 | "proxy-addr": "~2.0.5", 736 | "qs": "6.7.0", 737 | "range-parser": "~1.2.1", 738 | "safe-buffer": "5.1.2", 739 | "send": "0.17.1", 740 | "serve-static": "1.14.1", 741 | "setprototypeof": "1.1.1", 742 | "statuses": "~1.5.0", 743 | "type-is": "~1.6.18", 744 | "utils-merge": "1.0.1", 745 | "vary": "~1.1.2" 746 | } 747 | }, 748 | "feature-policy": { 749 | "version": "0.3.0", 750 | "resolved": "https://registry.npmjs.org/feature-policy/-/feature-policy-0.3.0.tgz", 751 | "integrity": "sha512-ZtijOTFN7TzCujt1fnNhfWPFPSHeZkesff9AXZj+UEjYBynWNUIYpC87Ve4wHzyexQsImicLu7WsC2LHq7/xrQ==" 752 | }, 753 | "fill-range": { 754 | "version": "7.0.1", 755 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 756 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 757 | "dev": true, 758 | "requires": { 759 | "to-regex-range": "^5.0.1" 760 | } 761 | }, 762 | "finalhandler": { 763 | "version": "1.1.2", 764 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 765 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 766 | "requires": { 767 | "debug": "2.6.9", 768 | "encodeurl": "~1.0.2", 769 | "escape-html": "~1.0.3", 770 | "on-finished": "~2.3.0", 771 | "parseurl": "~1.3.3", 772 | "statuses": "~1.5.0", 773 | "unpipe": "~1.0.0" 774 | } 775 | }, 776 | "forwarded": { 777 | "version": "0.1.2", 778 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 779 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 780 | }, 781 | "fresh": { 782 | "version": "0.5.2", 783 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 784 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 785 | }, 786 | "fs-minipass": { 787 | "version": "1.2.7", 788 | "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", 789 | "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", 790 | "requires": { 791 | "minipass": "^2.6.0" 792 | } 793 | }, 794 | "fs.realpath": { 795 | "version": "1.0.0", 796 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 797 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 798 | }, 799 | "gauge": { 800 | "version": "2.7.4", 801 | "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", 802 | "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", 803 | "requires": { 804 | "aproba": "^1.0.3", 805 | "console-control-strings": "^1.0.0", 806 | "has-unicode": "^2.0.0", 807 | "object-assign": "^4.1.0", 808 | "signal-exit": "^3.0.0", 809 | "string-width": "^1.0.1", 810 | "strip-ansi": "^3.0.1", 811 | "wide-align": "^1.1.0" 812 | } 813 | }, 814 | "generate-function": { 815 | "version": "2.3.1", 816 | "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", 817 | "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", 818 | "requires": { 819 | "is-property": "^1.0.2" 820 | } 821 | }, 822 | "get-own-enumerable-property-symbols": { 823 | "version": "3.0.2", 824 | "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", 825 | "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", 826 | "dev": true 827 | }, 828 | "get-stream": { 829 | "version": "6.0.1", 830 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", 831 | "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", 832 | "dev": true 833 | }, 834 | "glob": { 835 | "version": "7.1.6", 836 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 837 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 838 | "requires": { 839 | "fs.realpath": "^1.0.0", 840 | "inflight": "^1.0.4", 841 | "inherits": "2", 842 | "minimatch": "^3.0.4", 843 | "once": "^1.3.0", 844 | "path-is-absolute": "^1.0.0" 845 | } 846 | }, 847 | "has-flag": { 848 | "version": "4.0.0", 849 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 850 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 851 | "dev": true 852 | }, 853 | "has-unicode": { 854 | "version": "2.0.1", 855 | "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", 856 | "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" 857 | }, 858 | "helmet": { 859 | "version": "3.23.3", 860 | "resolved": "https://registry.npmjs.org/helmet/-/helmet-3.23.3.tgz", 861 | "integrity": "sha512-U3MeYdzPJQhtvqAVBPntVgAvNSOJyagwZwyKsFdyRa8TV3pOKVFljalPOCxbw5Wwf2kncGhmP0qHjyazIdNdSA==", 862 | "requires": { 863 | "depd": "2.0.0", 864 | "dont-sniff-mimetype": "1.1.0", 865 | "feature-policy": "0.3.0", 866 | "helmet-crossdomain": "0.4.0", 867 | "helmet-csp": "2.10.0", 868 | "hide-powered-by": "1.1.0", 869 | "hpkp": "2.0.0", 870 | "hsts": "2.2.0", 871 | "nocache": "2.1.0", 872 | "referrer-policy": "1.2.0", 873 | "x-xss-protection": "1.3.0" 874 | }, 875 | "dependencies": { 876 | "depd": { 877 | "version": "2.0.0", 878 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 879 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" 880 | } 881 | } 882 | }, 883 | "helmet-crossdomain": { 884 | "version": "0.4.0", 885 | "resolved": "https://registry.npmjs.org/helmet-crossdomain/-/helmet-crossdomain-0.4.0.tgz", 886 | "integrity": "sha512-AB4DTykRw3HCOxovD1nPR16hllrVImeFp5VBV9/twj66lJ2nU75DP8FPL0/Jp4jj79JhTfG+pFI2MD02kWJ+fA==" 887 | }, 888 | "helmet-csp": { 889 | "version": "2.10.0", 890 | "resolved": "https://registry.npmjs.org/helmet-csp/-/helmet-csp-2.10.0.tgz", 891 | "integrity": "sha512-Rz953ZNEFk8sT2XvewXkYN0Ho4GEZdjAZy4stjiEQV3eN7GDxg1QKmYggH7otDyIA7uGA6XnUMVSgeJwbR5X+w==", 892 | "requires": { 893 | "bowser": "2.9.0", 894 | "camelize": "1.0.0", 895 | "content-security-policy-builder": "2.1.0", 896 | "dasherize": "2.0.0" 897 | } 898 | }, 899 | "hide-powered-by": { 900 | "version": "1.1.0", 901 | "resolved": "https://registry.npmjs.org/hide-powered-by/-/hide-powered-by-1.1.0.tgz", 902 | "integrity": "sha512-Io1zA2yOA1YJslkr+AJlWSf2yWFkKjvkcL9Ni1XSUqnGLr/qRQe2UI3Cn/J9MsJht7yEVCe0SscY1HgVMujbgg==" 903 | }, 904 | "hpkp": { 905 | "version": "2.0.0", 906 | "resolved": "https://registry.npmjs.org/hpkp/-/hpkp-2.0.0.tgz", 907 | "integrity": "sha1-EOFCJk52IVpdMMROxD3mTe5tFnI=" 908 | }, 909 | "hsts": { 910 | "version": "2.2.0", 911 | "resolved": "https://registry.npmjs.org/hsts/-/hsts-2.2.0.tgz", 912 | "integrity": "sha512-ToaTnQ2TbJkochoVcdXYm4HOCliNozlviNsg+X2XQLQvZNI/kCHR9rZxVYpJB3UPcHz80PgxRyWQ7PdU1r+VBQ==", 913 | "requires": { 914 | "depd": "2.0.0" 915 | }, 916 | "dependencies": { 917 | "depd": { 918 | "version": "2.0.0", 919 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 920 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" 921 | } 922 | } 923 | }, 924 | "http-errors": { 925 | "version": "1.7.2", 926 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 927 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 928 | "requires": { 929 | "depd": "~1.1.2", 930 | "inherits": "2.0.3", 931 | "setprototypeof": "1.1.1", 932 | "statuses": ">= 1.5.0 < 2", 933 | "toidentifier": "1.0.0" 934 | } 935 | }, 936 | "human-signals": { 937 | "version": "2.1.0", 938 | "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", 939 | "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", 940 | "dev": true 941 | }, 942 | "husky": { 943 | "version": "7.0.1", 944 | "resolved": "https://registry.npmjs.org/husky/-/husky-7.0.1.tgz", 945 | "integrity": "sha512-gceRaITVZ+cJH9sNHqx5tFwbzlLCVxtVZcusME8JYQ8Edy5mpGDOqD8QBCdMhpyo9a+JXddnujQ4rpY2Ff9SJA==", 946 | "dev": true 947 | }, 948 | "iconv-lite": { 949 | "version": "0.4.24", 950 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 951 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 952 | "requires": { 953 | "safer-buffer": ">= 2.1.2 < 3" 954 | } 955 | }, 956 | "ignore-walk": { 957 | "version": "3.0.3", 958 | "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", 959 | "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", 960 | "requires": { 961 | "minimatch": "^3.0.4" 962 | } 963 | }, 964 | "image-size": { 965 | "version": "0.7.5", 966 | "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.7.5.tgz", 967 | "integrity": "sha512-Hiyv+mXHfFEP7LzUL/llg9RwFxxY+o9N3JVLIeG5E7iFIFAalxvRU9UZthBdYDEVnzHMgjnKJPPpay5BWf1g9g==" 968 | }, 969 | "import-fresh": { 970 | "version": "3.3.0", 971 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", 972 | "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", 973 | "dev": true, 974 | "requires": { 975 | "parent-module": "^1.0.0", 976 | "resolve-from": "^4.0.0" 977 | } 978 | }, 979 | "indent-string": { 980 | "version": "4.0.0", 981 | "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", 982 | "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", 983 | "dev": true 984 | }, 985 | "inflight": { 986 | "version": "1.0.6", 987 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 988 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 989 | "requires": { 990 | "once": "^1.3.0", 991 | "wrappy": "1" 992 | } 993 | }, 994 | "inherits": { 995 | "version": "2.0.3", 996 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 997 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 998 | }, 999 | "ini": { 1000 | "version": "1.3.7", 1001 | "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", 1002 | "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==" 1003 | }, 1004 | "ipaddr.js": { 1005 | "version": "1.9.0", 1006 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", 1007 | "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" 1008 | }, 1009 | "is-arrayish": { 1010 | "version": "0.2.1", 1011 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 1012 | "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", 1013 | "dev": true 1014 | }, 1015 | "is-fullwidth-code-point": { 1016 | "version": "1.0.0", 1017 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 1018 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 1019 | "requires": { 1020 | "number-is-nan": "^1.0.0" 1021 | } 1022 | }, 1023 | "is-number": { 1024 | "version": "7.0.0", 1025 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 1026 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 1027 | "dev": true 1028 | }, 1029 | "is-obj": { 1030 | "version": "1.0.1", 1031 | "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", 1032 | "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", 1033 | "dev": true 1034 | }, 1035 | "is-property": { 1036 | "version": "1.0.2", 1037 | "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", 1038 | "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" 1039 | }, 1040 | "is-regexp": { 1041 | "version": "1.0.0", 1042 | "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", 1043 | "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", 1044 | "dev": true 1045 | }, 1046 | "is-stream": { 1047 | "version": "2.0.1", 1048 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", 1049 | "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", 1050 | "dev": true 1051 | }, 1052 | "is-unicode-supported": { 1053 | "version": "0.1.0", 1054 | "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", 1055 | "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", 1056 | "dev": true 1057 | }, 1058 | "isarray": { 1059 | "version": "1.0.0", 1060 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 1061 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 1062 | }, 1063 | "isexe": { 1064 | "version": "2.0.0", 1065 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1066 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 1067 | "dev": true 1068 | }, 1069 | "js-tokens": { 1070 | "version": "4.0.0", 1071 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 1072 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 1073 | "dev": true 1074 | }, 1075 | "json-parse-even-better-errors": { 1076 | "version": "2.3.1", 1077 | "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", 1078 | "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", 1079 | "dev": true 1080 | }, 1081 | "jsonwebtoken": { 1082 | "version": "8.5.1", 1083 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", 1084 | "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", 1085 | "requires": { 1086 | "jws": "^3.2.2", 1087 | "lodash.includes": "^4.3.0", 1088 | "lodash.isboolean": "^3.0.3", 1089 | "lodash.isinteger": "^4.0.4", 1090 | "lodash.isnumber": "^3.0.3", 1091 | "lodash.isplainobject": "^4.0.6", 1092 | "lodash.isstring": "^4.0.1", 1093 | "lodash.once": "^4.0.0", 1094 | "ms": "^2.1.1", 1095 | "semver": "^5.6.0" 1096 | }, 1097 | "dependencies": { 1098 | "ms": { 1099 | "version": "2.1.2", 1100 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1101 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 1102 | }, 1103 | "semver": { 1104 | "version": "5.7.1", 1105 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 1106 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 1107 | } 1108 | } 1109 | }, 1110 | "jwa": { 1111 | "version": "1.4.1", 1112 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", 1113 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", 1114 | "requires": { 1115 | "buffer-equal-constant-time": "1.0.1", 1116 | "ecdsa-sig-formatter": "1.0.11", 1117 | "safe-buffer": "^5.0.1" 1118 | } 1119 | }, 1120 | "jws": { 1121 | "version": "3.2.2", 1122 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", 1123 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", 1124 | "requires": { 1125 | "jwa": "^1.4.1", 1126 | "safe-buffer": "^5.0.1" 1127 | } 1128 | }, 1129 | "lines-and-columns": { 1130 | "version": "1.1.6", 1131 | "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", 1132 | "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", 1133 | "dev": true 1134 | }, 1135 | "lint-staged": { 1136 | "version": "11.1.2", 1137 | "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-11.1.2.tgz", 1138 | "integrity": "sha512-6lYpNoA9wGqkL6Hew/4n1H6lRqF3qCsujVT0Oq5Z4hiSAM7S6NksPJ3gnr7A7R52xCtiZMcEUNNQ6d6X5Bvh9w==", 1139 | "dev": true, 1140 | "requires": { 1141 | "chalk": "^4.1.1", 1142 | "cli-truncate": "^2.1.0", 1143 | "commander": "^7.2.0", 1144 | "cosmiconfig": "^7.0.0", 1145 | "debug": "^4.3.1", 1146 | "enquirer": "^2.3.6", 1147 | "execa": "^5.0.0", 1148 | "listr2": "^3.8.2", 1149 | "log-symbols": "^4.1.0", 1150 | "micromatch": "^4.0.4", 1151 | "normalize-path": "^3.0.0", 1152 | "please-upgrade-node": "^3.2.0", 1153 | "string-argv": "0.3.1", 1154 | "stringify-object": "^3.3.0" 1155 | }, 1156 | "dependencies": { 1157 | "debug": { 1158 | "version": "4.3.2", 1159 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", 1160 | "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", 1161 | "dev": true, 1162 | "requires": { 1163 | "ms": "2.1.2" 1164 | } 1165 | }, 1166 | "ms": { 1167 | "version": "2.1.2", 1168 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1169 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1170 | "dev": true 1171 | } 1172 | } 1173 | }, 1174 | "listr2": { 1175 | "version": "3.11.0", 1176 | "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.11.0.tgz", 1177 | "integrity": "sha512-XLJVe2JgXCyQTa3FbSv11lkKExYmEyA4jltVo8z4FX10Vt1Yj8IMekBfwim0BSOM9uj1QMTJvDQQpHyuPbB/dQ==", 1178 | "dev": true, 1179 | "requires": { 1180 | "cli-truncate": "^2.1.0", 1181 | "colorette": "^1.2.2", 1182 | "log-update": "^4.0.0", 1183 | "p-map": "^4.0.0", 1184 | "rxjs": "^6.6.7", 1185 | "through": "^2.3.8", 1186 | "wrap-ansi": "^7.0.0" 1187 | } 1188 | }, 1189 | "lodash": { 1190 | "version": "4.17.21", 1191 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 1192 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 1193 | }, 1194 | "lodash.includes": { 1195 | "version": "4.3.0", 1196 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", 1197 | "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" 1198 | }, 1199 | "lodash.isboolean": { 1200 | "version": "3.0.3", 1201 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", 1202 | "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" 1203 | }, 1204 | "lodash.isinteger": { 1205 | "version": "4.0.4", 1206 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", 1207 | "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" 1208 | }, 1209 | "lodash.isnumber": { 1210 | "version": "3.0.3", 1211 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", 1212 | "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" 1213 | }, 1214 | "lodash.isplainobject": { 1215 | "version": "4.0.6", 1216 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", 1217 | "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" 1218 | }, 1219 | "lodash.isstring": { 1220 | "version": "4.0.1", 1221 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", 1222 | "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" 1223 | }, 1224 | "lodash.once": { 1225 | "version": "4.1.1", 1226 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", 1227 | "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" 1228 | }, 1229 | "log-symbols": { 1230 | "version": "4.1.0", 1231 | "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", 1232 | "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", 1233 | "dev": true, 1234 | "requires": { 1235 | "chalk": "^4.1.0", 1236 | "is-unicode-supported": "^0.1.0" 1237 | } 1238 | }, 1239 | "log-update": { 1240 | "version": "4.0.0", 1241 | "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", 1242 | "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", 1243 | "dev": true, 1244 | "requires": { 1245 | "ansi-escapes": "^4.3.0", 1246 | "cli-cursor": "^3.1.0", 1247 | "slice-ansi": "^4.0.0", 1248 | "wrap-ansi": "^6.2.0" 1249 | }, 1250 | "dependencies": { 1251 | "ansi-regex": { 1252 | "version": "5.0.0", 1253 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", 1254 | "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", 1255 | "dev": true 1256 | }, 1257 | "is-fullwidth-code-point": { 1258 | "version": "3.0.0", 1259 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 1260 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 1261 | "dev": true 1262 | }, 1263 | "slice-ansi": { 1264 | "version": "4.0.0", 1265 | "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", 1266 | "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", 1267 | "dev": true, 1268 | "requires": { 1269 | "ansi-styles": "^4.0.0", 1270 | "astral-regex": "^2.0.0", 1271 | "is-fullwidth-code-point": "^3.0.0" 1272 | } 1273 | }, 1274 | "string-width": { 1275 | "version": "4.2.2", 1276 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", 1277 | "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", 1278 | "dev": true, 1279 | "requires": { 1280 | "emoji-regex": "^8.0.0", 1281 | "is-fullwidth-code-point": "^3.0.0", 1282 | "strip-ansi": "^6.0.0" 1283 | } 1284 | }, 1285 | "strip-ansi": { 1286 | "version": "6.0.0", 1287 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", 1288 | "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", 1289 | "dev": true, 1290 | "requires": { 1291 | "ansi-regex": "^5.0.0" 1292 | } 1293 | }, 1294 | "wrap-ansi": { 1295 | "version": "6.2.0", 1296 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", 1297 | "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", 1298 | "dev": true, 1299 | "requires": { 1300 | "ansi-styles": "^4.0.0", 1301 | "string-width": "^4.1.0", 1302 | "strip-ansi": "^6.0.0" 1303 | } 1304 | } 1305 | } 1306 | }, 1307 | "long": { 1308 | "version": "4.0.0", 1309 | "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", 1310 | "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" 1311 | }, 1312 | "lru-cache": { 1313 | "version": "6.0.0", 1314 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 1315 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 1316 | "requires": { 1317 | "yallist": "^4.0.0" 1318 | }, 1319 | "dependencies": { 1320 | "yallist": { 1321 | "version": "4.0.0", 1322 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 1323 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 1324 | } 1325 | } 1326 | }, 1327 | "media-typer": { 1328 | "version": "0.3.0", 1329 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 1330 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 1331 | }, 1332 | "merge-descriptors": { 1333 | "version": "1.0.1", 1334 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 1335 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 1336 | }, 1337 | "merge-stream": { 1338 | "version": "2.0.0", 1339 | "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", 1340 | "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", 1341 | "dev": true 1342 | }, 1343 | "methods": { 1344 | "version": "1.1.2", 1345 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 1346 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 1347 | }, 1348 | "micromatch": { 1349 | "version": "4.0.4", 1350 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", 1351 | "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", 1352 | "dev": true, 1353 | "requires": { 1354 | "braces": "^3.0.1", 1355 | "picomatch": "^2.2.3" 1356 | } 1357 | }, 1358 | "mime": { 1359 | "version": "1.6.0", 1360 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 1361 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 1362 | }, 1363 | "mime-db": { 1364 | "version": "1.43.0", 1365 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", 1366 | "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" 1367 | }, 1368 | "mime-types": { 1369 | "version": "2.1.26", 1370 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", 1371 | "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", 1372 | "requires": { 1373 | "mime-db": "1.43.0" 1374 | } 1375 | }, 1376 | "mimer": { 1377 | "version": "1.0.0", 1378 | "resolved": "https://registry.npmjs.org/mimer/-/mimer-1.0.0.tgz", 1379 | "integrity": "sha512-4ZJvCzfcwsBgPbkKXUzGoVZMWjv8IDIygkGzVc7uUYhgnK0t2LmGxxjdgH1i+pn0/KQfB5F/VKUJlfyTSOFQjg==" 1380 | }, 1381 | "mimic-fn": { 1382 | "version": "2.1.0", 1383 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", 1384 | "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", 1385 | "dev": true 1386 | }, 1387 | "minimatch": { 1388 | "version": "3.0.4", 1389 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 1390 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 1391 | "requires": { 1392 | "brace-expansion": "^1.1.7" 1393 | } 1394 | }, 1395 | "minimist": { 1396 | "version": "1.2.5", 1397 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 1398 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 1399 | }, 1400 | "minipass": { 1401 | "version": "2.9.0", 1402 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", 1403 | "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", 1404 | "requires": { 1405 | "safe-buffer": "^5.1.2", 1406 | "yallist": "^3.0.0" 1407 | } 1408 | }, 1409 | "minizlib": { 1410 | "version": "1.3.3", 1411 | "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", 1412 | "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", 1413 | "requires": { 1414 | "minipass": "^2.9.0" 1415 | } 1416 | }, 1417 | "mkdirp": { 1418 | "version": "0.5.5", 1419 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", 1420 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", 1421 | "requires": { 1422 | "minimist": "^1.2.5" 1423 | } 1424 | }, 1425 | "morgan": { 1426 | "version": "1.10.0", 1427 | "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", 1428 | "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", 1429 | "requires": { 1430 | "basic-auth": "~2.0.1", 1431 | "debug": "2.6.9", 1432 | "depd": "~2.0.0", 1433 | "on-finished": "~2.3.0", 1434 | "on-headers": "~1.0.2" 1435 | }, 1436 | "dependencies": { 1437 | "depd": { 1438 | "version": "2.0.0", 1439 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 1440 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" 1441 | } 1442 | } 1443 | }, 1444 | "ms": { 1445 | "version": "2.0.0", 1446 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1447 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 1448 | }, 1449 | "multer": { 1450 | "version": "1.4.2", 1451 | "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.2.tgz", 1452 | "integrity": "sha512-xY8pX7V+ybyUpbYMxtjM9KAiD9ixtg5/JkeKUTD6xilfDv0vzzOFcCp4Ljb1UU3tSOM3VTZtKo63OmzOrGi3Cg==", 1453 | "requires": { 1454 | "append-field": "^1.0.0", 1455 | "busboy": "^0.2.11", 1456 | "concat-stream": "^1.5.2", 1457 | "mkdirp": "^0.5.1", 1458 | "object-assign": "^4.1.1", 1459 | "on-finished": "^2.3.0", 1460 | "type-is": "^1.6.4", 1461 | "xtend": "^4.0.0" 1462 | } 1463 | }, 1464 | "mysql": { 1465 | "version": "2.18.1", 1466 | "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", 1467 | "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", 1468 | "requires": { 1469 | "bignumber.js": "9.0.0", 1470 | "readable-stream": "2.3.7", 1471 | "safe-buffer": "5.1.2", 1472 | "sqlstring": "2.3.1" 1473 | } 1474 | }, 1475 | "mysql2": { 1476 | "version": "2.2.5", 1477 | "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.2.5.tgz", 1478 | "integrity": "sha512-XRqPNxcZTpmFdXbJqb+/CtYVLCx14x1RTeNMD4954L331APu75IC74GDqnZMEt1kwaXy6TySo55rF2F3YJS78g==", 1479 | "requires": { 1480 | "denque": "^1.4.1", 1481 | "generate-function": "^2.3.1", 1482 | "iconv-lite": "^0.6.2", 1483 | "long": "^4.0.0", 1484 | "lru-cache": "^6.0.0", 1485 | "named-placeholders": "^1.1.2", 1486 | "seq-queue": "^0.0.5", 1487 | "sqlstring": "^2.3.2" 1488 | }, 1489 | "dependencies": { 1490 | "iconv-lite": { 1491 | "version": "0.6.2", 1492 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", 1493 | "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", 1494 | "requires": { 1495 | "safer-buffer": ">= 2.1.2 < 3.0.0" 1496 | } 1497 | }, 1498 | "sqlstring": { 1499 | "version": "2.3.2", 1500 | "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz", 1501 | "integrity": "sha512-vF4ZbYdKS8OnoJAWBmMxCQDkiEBkGQYU7UZPtL8flbDRSNkhaXvRJ279ZtI6M+zDaQovVU4tuRgzK5fVhvFAhg==" 1502 | } 1503 | } 1504 | }, 1505 | "named-placeholders": { 1506 | "version": "1.1.2", 1507 | "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.2.tgz", 1508 | "integrity": "sha512-wiFWqxoLL3PGVReSZpjLVxyJ1bRqe+KKJVbr4hGs1KWfTZTQyezHFBbuKj9hsizHyGV2ne7EMjHdxEGAybD5SA==", 1509 | "requires": { 1510 | "lru-cache": "^4.1.3" 1511 | }, 1512 | "dependencies": { 1513 | "lru-cache": { 1514 | "version": "4.1.5", 1515 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", 1516 | "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", 1517 | "requires": { 1518 | "pseudomap": "^1.0.2", 1519 | "yallist": "^2.1.2" 1520 | } 1521 | }, 1522 | "yallist": { 1523 | "version": "2.1.2", 1524 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", 1525 | "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" 1526 | } 1527 | } 1528 | }, 1529 | "needle": { 1530 | "version": "2.5.2", 1531 | "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.2.tgz", 1532 | "integrity": "sha512-LbRIwS9BfkPvNwNHlsA41Q29kL2L/6VaOJ0qisM5lLWsTV3nP15abO5ITL6L81zqFhzjRKDAYjpcBcwM0AVvLQ==", 1533 | "requires": { 1534 | "debug": "^3.2.6", 1535 | "iconv-lite": "^0.4.4", 1536 | "sax": "^1.2.4" 1537 | }, 1538 | "dependencies": { 1539 | "debug": { 1540 | "version": "3.2.6", 1541 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", 1542 | "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", 1543 | "requires": { 1544 | "ms": "^2.1.1" 1545 | } 1546 | }, 1547 | "ms": { 1548 | "version": "2.1.2", 1549 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1550 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 1551 | } 1552 | } 1553 | }, 1554 | "negotiator": { 1555 | "version": "0.6.2", 1556 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 1557 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 1558 | }, 1559 | "nocache": { 1560 | "version": "2.1.0", 1561 | "resolved": "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz", 1562 | "integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==" 1563 | }, 1564 | "node-addon-api": { 1565 | "version": "3.0.2", 1566 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.0.2.tgz", 1567 | "integrity": "sha512-+D4s2HCnxPd5PjjI0STKwncjXTUKKqm74MDMz9OPXavjsGmjkvwgLtA5yoxJUdmpj52+2u+RrXgPipahKczMKg==" 1568 | }, 1569 | "node-pre-gyp": { 1570 | "version": "0.15.0", 1571 | "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.15.0.tgz", 1572 | "integrity": "sha512-7QcZa8/fpaU/BKenjcaeFF9hLz2+7S9AqyXFhlH/rilsQ/hPZKK32RtR5EQHJElgu+q5RfbJ34KriI79UWaorA==", 1573 | "requires": { 1574 | "detect-libc": "^1.0.2", 1575 | "mkdirp": "^0.5.3", 1576 | "needle": "^2.5.0", 1577 | "nopt": "^4.0.1", 1578 | "npm-packlist": "^1.1.6", 1579 | "npmlog": "^4.0.2", 1580 | "rc": "^1.2.7", 1581 | "rimraf": "^2.6.1", 1582 | "semver": "^5.3.0", 1583 | "tar": "^4.4.2" 1584 | } 1585 | }, 1586 | "nodemailer": { 1587 | "version": "6.4.16", 1588 | "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.16.tgz", 1589 | "integrity": "sha512-68K0LgZ6hmZ7PVmwL78gzNdjpj5viqBdFqKrTtr9bZbJYj6BRj5W6WGkxXrEnUl3Co3CBXi3CZBUlpV/foGnOQ==" 1590 | }, 1591 | "nopt": { 1592 | "version": "4.0.3", 1593 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", 1594 | "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", 1595 | "requires": { 1596 | "abbrev": "1", 1597 | "osenv": "^0.1.4" 1598 | } 1599 | }, 1600 | "normalize-path": { 1601 | "version": "3.0.0", 1602 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 1603 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 1604 | "dev": true 1605 | }, 1606 | "npm-bundled": { 1607 | "version": "1.1.1", 1608 | "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", 1609 | "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", 1610 | "requires": { 1611 | "npm-normalize-package-bin": "^1.0.1" 1612 | } 1613 | }, 1614 | "npm-normalize-package-bin": { 1615 | "version": "1.0.1", 1616 | "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", 1617 | "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" 1618 | }, 1619 | "npm-packlist": { 1620 | "version": "1.4.8", 1621 | "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", 1622 | "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", 1623 | "requires": { 1624 | "ignore-walk": "^3.0.1", 1625 | "npm-bundled": "^1.0.1", 1626 | "npm-normalize-package-bin": "^1.0.1" 1627 | } 1628 | }, 1629 | "npm-run-path": { 1630 | "version": "4.0.1", 1631 | "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", 1632 | "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", 1633 | "dev": true, 1634 | "requires": { 1635 | "path-key": "^3.0.0" 1636 | } 1637 | }, 1638 | "npmlog": { 1639 | "version": "4.1.2", 1640 | "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", 1641 | "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", 1642 | "requires": { 1643 | "are-we-there-yet": "~1.1.2", 1644 | "console-control-strings": "~1.1.0", 1645 | "gauge": "~2.7.3", 1646 | "set-blocking": "~2.0.0" 1647 | } 1648 | }, 1649 | "number-is-nan": { 1650 | "version": "1.0.1", 1651 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 1652 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" 1653 | }, 1654 | "object-assign": { 1655 | "version": "4.1.1", 1656 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1657 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 1658 | }, 1659 | "on-finished": { 1660 | "version": "2.3.0", 1661 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 1662 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 1663 | "requires": { 1664 | "ee-first": "1.1.1" 1665 | } 1666 | }, 1667 | "on-headers": { 1668 | "version": "1.0.2", 1669 | "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", 1670 | "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" 1671 | }, 1672 | "once": { 1673 | "version": "1.4.0", 1674 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1675 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1676 | "requires": { 1677 | "wrappy": "1" 1678 | } 1679 | }, 1680 | "onetime": { 1681 | "version": "5.1.2", 1682 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", 1683 | "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", 1684 | "dev": true, 1685 | "requires": { 1686 | "mimic-fn": "^2.1.0" 1687 | } 1688 | }, 1689 | "os-homedir": { 1690 | "version": "1.0.2", 1691 | "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", 1692 | "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" 1693 | }, 1694 | "os-tmpdir": { 1695 | "version": "1.0.2", 1696 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 1697 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" 1698 | }, 1699 | "osenv": { 1700 | "version": "0.1.5", 1701 | "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", 1702 | "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", 1703 | "requires": { 1704 | "os-homedir": "^1.0.0", 1705 | "os-tmpdir": "^1.0.0" 1706 | } 1707 | }, 1708 | "p-map": { 1709 | "version": "4.0.0", 1710 | "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", 1711 | "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", 1712 | "dev": true, 1713 | "requires": { 1714 | "aggregate-error": "^3.0.0" 1715 | } 1716 | }, 1717 | "parent-module": { 1718 | "version": "1.0.1", 1719 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 1720 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 1721 | "dev": true, 1722 | "requires": { 1723 | "callsites": "^3.0.0" 1724 | } 1725 | }, 1726 | "parse-json": { 1727 | "version": "5.2.0", 1728 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", 1729 | "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", 1730 | "dev": true, 1731 | "requires": { 1732 | "@babel/code-frame": "^7.0.0", 1733 | "error-ex": "^1.3.1", 1734 | "json-parse-even-better-errors": "^2.3.0", 1735 | "lines-and-columns": "^1.1.6" 1736 | } 1737 | }, 1738 | "parseurl": { 1739 | "version": "1.3.3", 1740 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1741 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 1742 | }, 1743 | "path-is-absolute": { 1744 | "version": "1.0.1", 1745 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1746 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 1747 | }, 1748 | "path-key": { 1749 | "version": "3.1.1", 1750 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 1751 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 1752 | "dev": true 1753 | }, 1754 | "path-to-regexp": { 1755 | "version": "0.1.7", 1756 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 1757 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 1758 | }, 1759 | "path-type": { 1760 | "version": "4.0.0", 1761 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", 1762 | "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", 1763 | "dev": true 1764 | }, 1765 | "picomatch": { 1766 | "version": "2.3.0", 1767 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", 1768 | "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", 1769 | "dev": true 1770 | }, 1771 | "please-upgrade-node": { 1772 | "version": "3.2.0", 1773 | "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", 1774 | "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", 1775 | "dev": true, 1776 | "requires": { 1777 | "semver-compare": "^1.0.0" 1778 | } 1779 | }, 1780 | "prettier": { 1781 | "version": "2.3.2", 1782 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", 1783 | "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==", 1784 | "dev": true 1785 | }, 1786 | "process-nextick-args": { 1787 | "version": "2.0.1", 1788 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 1789 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 1790 | }, 1791 | "proxy-addr": { 1792 | "version": "2.0.5", 1793 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", 1794 | "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", 1795 | "requires": { 1796 | "forwarded": "~0.1.2", 1797 | "ipaddr.js": "1.9.0" 1798 | } 1799 | }, 1800 | "pseudomap": { 1801 | "version": "1.0.2", 1802 | "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", 1803 | "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" 1804 | }, 1805 | "python-shell": { 1806 | "version": "1.0.8", 1807 | "resolved": "https://registry.npmjs.org/python-shell/-/python-shell-1.0.8.tgz", 1808 | "integrity": "sha512-jMKagerg3alm6j+Prq5t/M3dTgEppy5vC6ns+LqAjfuHiT8olfK3PMokpqpeEcWEqvDnUcAOhp6SQzaLBtTzRw==" 1809 | }, 1810 | "q": { 1811 | "version": "1.5.1", 1812 | "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", 1813 | "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" 1814 | }, 1815 | "qs": { 1816 | "version": "6.7.0", 1817 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 1818 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 1819 | }, 1820 | "range-parser": { 1821 | "version": "1.2.1", 1822 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1823 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 1824 | }, 1825 | "raw-body": { 1826 | "version": "2.4.0", 1827 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 1828 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 1829 | "requires": { 1830 | "bytes": "3.1.0", 1831 | "http-errors": "1.7.2", 1832 | "iconv-lite": "0.4.24", 1833 | "unpipe": "1.0.0" 1834 | } 1835 | }, 1836 | "rc": { 1837 | "version": "1.2.8", 1838 | "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", 1839 | "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", 1840 | "requires": { 1841 | "deep-extend": "^0.6.0", 1842 | "ini": "~1.3.0", 1843 | "minimist": "^1.2.0", 1844 | "strip-json-comments": "~2.0.1" 1845 | } 1846 | }, 1847 | "readable-stream": { 1848 | "version": "2.3.7", 1849 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 1850 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 1851 | "requires": { 1852 | "core-util-is": "~1.0.0", 1853 | "inherits": "~2.0.3", 1854 | "isarray": "~1.0.0", 1855 | "process-nextick-args": "~2.0.0", 1856 | "safe-buffer": "~5.1.1", 1857 | "string_decoder": "~1.1.1", 1858 | "util-deprecate": "~1.0.1" 1859 | } 1860 | }, 1861 | "referrer-policy": { 1862 | "version": "1.2.0", 1863 | "resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.2.0.tgz", 1864 | "integrity": "sha512-LgQJIuS6nAy1Jd88DCQRemyE3mS+ispwlqMk3b0yjZ257fI1v9c+/p6SD5gP5FGyXUIgrNOAfmyioHwZtYv2VA==" 1865 | }, 1866 | "resolve-from": { 1867 | "version": "4.0.0", 1868 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 1869 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 1870 | "dev": true 1871 | }, 1872 | "restore-cursor": { 1873 | "version": "3.1.0", 1874 | "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", 1875 | "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", 1876 | "dev": true, 1877 | "requires": { 1878 | "onetime": "^5.1.0", 1879 | "signal-exit": "^3.0.2" 1880 | } 1881 | }, 1882 | "rimraf": { 1883 | "version": "2.7.1", 1884 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", 1885 | "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", 1886 | "requires": { 1887 | "glob": "^7.1.3" 1888 | } 1889 | }, 1890 | "rxjs": { 1891 | "version": "6.6.7", 1892 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", 1893 | "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", 1894 | "dev": true, 1895 | "requires": { 1896 | "tslib": "^1.9.0" 1897 | } 1898 | }, 1899 | "safe-buffer": { 1900 | "version": "5.1.2", 1901 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1902 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 1903 | }, 1904 | "safer-buffer": { 1905 | "version": "2.1.2", 1906 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1907 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1908 | }, 1909 | "sax": { 1910 | "version": "1.2.4", 1911 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", 1912 | "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" 1913 | }, 1914 | "semver": { 1915 | "version": "5.7.1", 1916 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 1917 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" 1918 | }, 1919 | "semver-compare": { 1920 | "version": "1.0.0", 1921 | "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", 1922 | "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", 1923 | "dev": true 1924 | }, 1925 | "send": { 1926 | "version": "0.17.1", 1927 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 1928 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 1929 | "requires": { 1930 | "debug": "2.6.9", 1931 | "depd": "~1.1.2", 1932 | "destroy": "~1.0.4", 1933 | "encodeurl": "~1.0.2", 1934 | "escape-html": "~1.0.3", 1935 | "etag": "~1.8.1", 1936 | "fresh": "0.5.2", 1937 | "http-errors": "~1.7.2", 1938 | "mime": "1.6.0", 1939 | "ms": "2.1.1", 1940 | "on-finished": "~2.3.0", 1941 | "range-parser": "~1.2.1", 1942 | "statuses": "~1.5.0" 1943 | }, 1944 | "dependencies": { 1945 | "ms": { 1946 | "version": "2.1.1", 1947 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 1948 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 1949 | } 1950 | } 1951 | }, 1952 | "seq-queue": { 1953 | "version": "0.0.5", 1954 | "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", 1955 | "integrity": "sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4=" 1956 | }, 1957 | "serve-static": { 1958 | "version": "1.14.1", 1959 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 1960 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 1961 | "requires": { 1962 | "encodeurl": "~1.0.2", 1963 | "escape-html": "~1.0.3", 1964 | "parseurl": "~1.3.3", 1965 | "send": "0.17.1" 1966 | } 1967 | }, 1968 | "set-blocking": { 1969 | "version": "2.0.0", 1970 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 1971 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" 1972 | }, 1973 | "setprototypeof": { 1974 | "version": "1.1.1", 1975 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 1976 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 1977 | }, 1978 | "shebang-command": { 1979 | "version": "2.0.0", 1980 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 1981 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 1982 | "dev": true, 1983 | "requires": { 1984 | "shebang-regex": "^3.0.0" 1985 | } 1986 | }, 1987 | "shebang-regex": { 1988 | "version": "3.0.0", 1989 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 1990 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 1991 | "dev": true 1992 | }, 1993 | "signal-exit": { 1994 | "version": "3.0.3", 1995 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", 1996 | "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" 1997 | }, 1998 | "slice-ansi": { 1999 | "version": "3.0.0", 2000 | "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", 2001 | "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", 2002 | "dev": true, 2003 | "requires": { 2004 | "ansi-styles": "^4.0.0", 2005 | "astral-regex": "^2.0.0", 2006 | "is-fullwidth-code-point": "^3.0.0" 2007 | }, 2008 | "dependencies": { 2009 | "is-fullwidth-code-point": { 2010 | "version": "3.0.0", 2011 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 2012 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 2013 | "dev": true 2014 | } 2015 | } 2016 | }, 2017 | "sqlstring": { 2018 | "version": "2.3.1", 2019 | "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", 2020 | "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=" 2021 | }, 2022 | "statuses": { 2023 | "version": "1.5.0", 2024 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 2025 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 2026 | }, 2027 | "streamsearch": { 2028 | "version": "0.1.2", 2029 | "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", 2030 | "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" 2031 | }, 2032 | "string-argv": { 2033 | "version": "0.3.1", 2034 | "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", 2035 | "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", 2036 | "dev": true 2037 | }, 2038 | "string-width": { 2039 | "version": "1.0.2", 2040 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 2041 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 2042 | "requires": { 2043 | "code-point-at": "^1.0.0", 2044 | "is-fullwidth-code-point": "^1.0.0", 2045 | "strip-ansi": "^3.0.0" 2046 | } 2047 | }, 2048 | "string_decoder": { 2049 | "version": "1.1.1", 2050 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 2051 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 2052 | "requires": { 2053 | "safe-buffer": "~5.1.0" 2054 | } 2055 | }, 2056 | "stringify-object": { 2057 | "version": "3.3.0", 2058 | "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", 2059 | "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", 2060 | "dev": true, 2061 | "requires": { 2062 | "get-own-enumerable-property-symbols": "^3.0.0", 2063 | "is-obj": "^1.0.1", 2064 | "is-regexp": "^1.0.0" 2065 | } 2066 | }, 2067 | "strip-ansi": { 2068 | "version": "3.0.1", 2069 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 2070 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 2071 | "requires": { 2072 | "ansi-regex": "^2.0.0" 2073 | } 2074 | }, 2075 | "strip-final-newline": { 2076 | "version": "2.0.0", 2077 | "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", 2078 | "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", 2079 | "dev": true 2080 | }, 2081 | "strip-json-comments": { 2082 | "version": "2.0.1", 2083 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 2084 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" 2085 | }, 2086 | "supports-color": { 2087 | "version": "7.2.0", 2088 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 2089 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 2090 | "dev": true, 2091 | "requires": { 2092 | "has-flag": "^4.0.0" 2093 | } 2094 | }, 2095 | "tar": { 2096 | "version": "4.4.19", 2097 | "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", 2098 | "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", 2099 | "requires": { 2100 | "chownr": "^1.1.4", 2101 | "fs-minipass": "^1.2.7", 2102 | "minipass": "^2.9.0", 2103 | "minizlib": "^1.3.3", 2104 | "mkdirp": "^0.5.5", 2105 | "safe-buffer": "^5.2.1", 2106 | "yallist": "^3.1.1" 2107 | }, 2108 | "dependencies": { 2109 | "safe-buffer": { 2110 | "version": "5.2.1", 2111 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 2112 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 2113 | } 2114 | } 2115 | }, 2116 | "through": { 2117 | "version": "2.3.8", 2118 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 2119 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 2120 | "dev": true 2121 | }, 2122 | "to-regex-range": { 2123 | "version": "5.0.1", 2124 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 2125 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 2126 | "dev": true, 2127 | "requires": { 2128 | "is-number": "^7.0.0" 2129 | } 2130 | }, 2131 | "toidentifier": { 2132 | "version": "1.0.0", 2133 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 2134 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 2135 | }, 2136 | "tslib": { 2137 | "version": "1.14.1", 2138 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", 2139 | "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", 2140 | "dev": true 2141 | }, 2142 | "type-fest": { 2143 | "version": "0.21.3", 2144 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", 2145 | "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", 2146 | "dev": true 2147 | }, 2148 | "type-is": { 2149 | "version": "1.6.18", 2150 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 2151 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 2152 | "requires": { 2153 | "media-typer": "0.3.0", 2154 | "mime-types": "~2.1.24" 2155 | } 2156 | }, 2157 | "typedarray": { 2158 | "version": "0.0.6", 2159 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 2160 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" 2161 | }, 2162 | "unpipe": { 2163 | "version": "1.0.0", 2164 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 2165 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 2166 | }, 2167 | "util-deprecate": { 2168 | "version": "1.0.2", 2169 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 2170 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 2171 | }, 2172 | "utils-merge": { 2173 | "version": "1.0.1", 2174 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 2175 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 2176 | }, 2177 | "vary": { 2178 | "version": "1.1.2", 2179 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 2180 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 2181 | }, 2182 | "which": { 2183 | "version": "2.0.2", 2184 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 2185 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 2186 | "dev": true, 2187 | "requires": { 2188 | "isexe": "^2.0.0" 2189 | } 2190 | }, 2191 | "wide-align": { 2192 | "version": "1.1.3", 2193 | "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", 2194 | "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", 2195 | "requires": { 2196 | "string-width": "^1.0.2 || 2" 2197 | } 2198 | }, 2199 | "wrap-ansi": { 2200 | "version": "7.0.0", 2201 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 2202 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 2203 | "dev": true, 2204 | "requires": { 2205 | "ansi-styles": "^4.0.0", 2206 | "string-width": "^4.1.0", 2207 | "strip-ansi": "^6.0.0" 2208 | }, 2209 | "dependencies": { 2210 | "ansi-regex": { 2211 | "version": "5.0.0", 2212 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", 2213 | "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", 2214 | "dev": true 2215 | }, 2216 | "is-fullwidth-code-point": { 2217 | "version": "3.0.0", 2218 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 2219 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 2220 | "dev": true 2221 | }, 2222 | "string-width": { 2223 | "version": "4.2.2", 2224 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", 2225 | "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", 2226 | "dev": true, 2227 | "requires": { 2228 | "emoji-regex": "^8.0.0", 2229 | "is-fullwidth-code-point": "^3.0.0", 2230 | "strip-ansi": "^6.0.0" 2231 | } 2232 | }, 2233 | "strip-ansi": { 2234 | "version": "6.0.0", 2235 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", 2236 | "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", 2237 | "dev": true, 2238 | "requires": { 2239 | "ansi-regex": "^5.0.0" 2240 | } 2241 | } 2242 | } 2243 | }, 2244 | "wrappy": { 2245 | "version": "1.0.2", 2246 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 2247 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 2248 | }, 2249 | "x-xss-protection": { 2250 | "version": "1.3.0", 2251 | "resolved": "https://registry.npmjs.org/x-xss-protection/-/x-xss-protection-1.3.0.tgz", 2252 | "integrity": "sha512-kpyBI9TlVipZO4diReZMAHWtS0MMa/7Kgx8hwG/EuZLiA6sg4Ah/4TRdASHhRRN3boobzcYgFRUFSgHRge6Qhg==" 2253 | }, 2254 | "xtend": { 2255 | "version": "4.0.2", 2256 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", 2257 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" 2258 | }, 2259 | "yallist": { 2260 | "version": "3.1.1", 2261 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", 2262 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" 2263 | }, 2264 | "yaml": { 2265 | "version": "1.10.2", 2266 | "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", 2267 | "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", 2268 | "dev": true 2269 | } 2270 | } 2271 | } 2272 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "breads", 3 | "version": "1.0.0", 4 | "private": "true", 5 | "description": "", 6 | "engines": { 7 | "node": "11.15.x" 8 | }, 9 | "main": "src/app.js", 10 | "scripts": { 11 | "prepare": "husky install", 12 | "start": "node src/app.js", 13 | "test": "echo \"Error: no test specified\" && exit 1" 14 | }, 15 | "keywords": [], 16 | "author": "", 17 | "license": "ISC", 18 | "dependencies": { 19 | "bcrypt": "^5.0.0", 20 | "body-parser": "^1.19.0", 21 | "cloudinary": "^1.23.0", 22 | "compression": "^1.7.4", 23 | "cors": "^2.8.5", 24 | "datauri": "^2.0.0", 25 | "dotenv": "^8.2.0", 26 | "express": "^4.17.1", 27 | "helmet": "^3.23.3", 28 | "jsonwebtoken": "^8.5.1", 29 | "morgan": "^1.10.0", 30 | "multer": "^1.4.2", 31 | "mysql": "^2.18.1", 32 | "mysql2": "^2.2.5", 33 | "nodemailer": "^6.4.16", 34 | "python-shell": "^1.0.8" 35 | }, 36 | "devDependencies": { 37 | "husky": "^7.0.1", 38 | "lint-staged": "^11.1.2", 39 | "prettier": "^2.3.2" 40 | }, 41 | "lint-staged": { 42 | "src/**/*": "prettier --write --ignore-unknown" 43 | }, 44 | "husky": { 45 | "hooks": { 46 | "pre-commit": "lint-staged" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | beautifulsoup4==4.9.3 2 | gensim==3.8.1 3 | python-dotenv==0.10.5 4 | requests==2.22.0 5 | fake_useragent==0.1.11 6 | goose3==3.1.6 7 | splinter==0.14.0 8 | selenium==3.141.0 -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | let express = require("express"), 3 | app = express(), 4 | helmet = require("helmet"), 5 | bodyParser = require("body-parser"), 6 | cors = require("cors"), 7 | compression = require("compression"), 8 | morgan = require("morgan"), 9 | errorHandler = require("./controllers/error").errorHandler, 10 | authRoutes = require("./routes/auth"), 11 | userRoutes = require("./routes/users"), 12 | readingRoutes = require("./routes/readings"), 13 | searchRoutes = require("./routes/search"), 14 | tagRoutes = require("./routes/tags"), 15 | cspReportRoutes = require("./routes/cspReport"); 16 | 17 | const PORT = process.env.PORT || 8080; 18 | 19 | // app.use(function (req, res, next) { 20 | // res.header('Access-Control-Allow-Origin', '*'); 21 | // res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS'); 22 | // res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With'); 23 | // // intercept OPTIONS method 24 | // if ('OPTIONS' == req.method) res.sendStatus(200); 25 | // else next(); 26 | // }); 27 | 28 | app.use(compression()); 29 | app.use(helmet()); 30 | app.use( 31 | helmet.contentSecurityPolicy({ 32 | directives: { 33 | defaultSrc: [ 34 | "'self'", 35 | "https://staging-breads-server.herokuapp.com", 36 | "https://breads-server.herokuapp.com", 37 | ], 38 | scriptSrc: [ 39 | "'self'", 40 | "https://staging-breads-server.herokuapp.com", 41 | "https://breads-server.herokuapp.com", 42 | ], 43 | }, 44 | }) 45 | ); 46 | app.use(bodyParser.json()); 47 | app.use(morgan("combined")); 48 | 49 | const whitelist = [ 50 | "https://www.breads.io", 51 | "https://staging-breads-client.herokuapp.com", 52 | "https://jw00ds.github.io", 53 | process.env.LOCAL_CORS, 54 | ]; 55 | const corsOptions = { 56 | origin: function (origin, callback) { 57 | const isWhiteListed = whitelist.indexOf(origin) !== -1; 58 | const isLocal = process.env.LOCAL_CORS === `localhost:${PORT}` && !origin; 59 | 60 | if (isWhiteListed || isLocal) { 61 | callback(null, true); 62 | } else { 63 | callback(new Error("Not allowed by CORS")); 64 | } 65 | }, 66 | }; 67 | 68 | app.use(cors(corsOptions)); 69 | // Enable complex CORS requests (DELETE/OPTIONS) 70 | app.options("*", cors()); 71 | 72 | app.use("/api/auth", authRoutes); 73 | app.use("/api/users", userRoutes); 74 | app.use("/api/readings", readingRoutes); 75 | app.use("/api/search", searchRoutes); 76 | app.use("/api/tags", tagRoutes); 77 | app.use("/api/csp-report", cspReportRoutes); 78 | 79 | // "Content-Security-Policy-Report-Only": "default-src 'self' https://staging-breads-server.herokuapp.com https://breads-server.herokuapp.com; script-src 'self' https://staging-breads-server.herokuapp.com https://breads-server.herokuapp.com 'sha256-IjRw88EIRqqX+VpFI3slzD4qzNuRp0RfxZuz50uE2eQ='; img-src 'self' https://images.unsplash.com http://res.cloudinary.com https://staging-breads-client.herokuapp.com https://www.breads.io data:; style-src 'self' 'sha256-UTjtaAWWTyzFjRKbltk24jHijlTbP20C1GUYaWPqg7E=' 'sha256-deDIoPlRijnpfbTDYsK+8JmDfUBmpwpnb0L/SUV8NeU='; report-uri https://staging-breads-server.herokuapp.com/api/csp-report", 80 | 81 | app.use(function (req, res, next) { 82 | let err = new Error("Not Found"); 83 | err.status = 404; 84 | next(err); 85 | }); 86 | 87 | app.use(errorHandler); 88 | 89 | app.listen(PORT, function () { 90 | console.log(`App listening on port ${PORT}!`); 91 | }); 92 | -------------------------------------------------------------------------------- /src/controllers/auth.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"), 2 | User = require("../models/user"), 3 | helpers = require("../helpers/auth"); 4 | 5 | exports.signup = async (req, res, next) => { 6 | try { 7 | const userImage = helpers.getImage(); 8 | const passwordHash = helpers.generateHash(req.body.password); 9 | 10 | const user = await User.create([ 11 | req.body.first_name, 12 | req.body.last_name, 13 | req.body.username, 14 | req.body.email, 15 | passwordHash, 16 | userImage, 17 | ]); 18 | 19 | const token = helpers.createToken( 20 | user.insertId, 21 | req.body.username, 22 | userImage 23 | ); 24 | 25 | return res.status(200).json({ 26 | id: user.insertId, 27 | username: req.body.username, 28 | image: userImage, 29 | token, 30 | }); 31 | } catch (err) { 32 | console.log("controllers/auth - signup"); 33 | console.log(err); 34 | if (err.code === "ER_DUP_ENTRY") { 35 | err.message = "Sorry, that username and/or email is taken"; 36 | } 37 | return next({ 38 | status: 400, 39 | message: err.message, 40 | }); 41 | } 42 | }; 43 | 44 | exports.signin = async (req, res, next) => { 45 | try { 46 | const username = req.body.username; 47 | const password = req.body.password; 48 | const user = await User.findByUsername(username); 49 | 50 | helpers.comparePassword(password, user[0].password, (err, isMatch) => { 51 | if (isMatch) { 52 | const token = helpers.createToken(user[0].id, username, user[0].image); 53 | 54 | return res.status(200).json({ 55 | id: user[0].id, 56 | username, 57 | image: user[0].image, 58 | token, 59 | }); 60 | } else { 61 | console.log("controllers/auth - signin"); 62 | console.log(err); 63 | return next({ 64 | status: 400, 65 | message: "Invalid Username/Password.", 66 | }); 67 | } 68 | }); 69 | } catch (err) { 70 | console.log("controllers/auth - signin"); 71 | console.log(err); 72 | return next({ 73 | status: 400, 74 | message: "Invalid Username/Password.", 75 | }); 76 | } 77 | }; 78 | 79 | exports.updatePassword = async (req, res, next) => { 80 | try { 81 | const { username, token } = req.params; 82 | const { password } = req.body; 83 | const user = await User.findByUsername(username); 84 | 85 | if (helpers.isRealUser(user[0], token)) { 86 | const hash = helpers.generateHash(password); 87 | await User.findByIdAndUpdatePassword(hash, user[0].id); 88 | return res.status(202).json({ 89 | message: "Password changed accepted", 90 | }); 91 | } else { 92 | console.log("controllers/auth - signin"); 93 | console.log(err); 94 | return next({ 95 | status: 400, 96 | message: "Invalid Username/Password.", 97 | }); 98 | } 99 | } catch (err) { 100 | console.log("controllers/auth - updatePassword"); 101 | console.log(err); 102 | return next({ 103 | status: 404, 104 | message: "Something went wrong", 105 | }); 106 | } 107 | }; 108 | 109 | exports.sendPasswordResetEmail = async (req, res, next) => { 110 | try { 111 | const user = await User.findByEmail(req.body.email); 112 | const token = helpers.getEmailToken(user[0]); 113 | const url = helpers.getPasswordResetURL(user[0].username, token); 114 | const emailTemplate = helpers.emailTemplate( 115 | user[0].email, 116 | user[0].firstname, 117 | url 118 | ); 119 | const sendEmail = helpers.sendEmail(emailTemplate, next); 120 | return res.status(200).json({ 121 | message: sendEmail, 122 | }); 123 | } catch (err) { 124 | return res.status(404).json({ 125 | message: "Error sending email", 126 | }); 127 | } 128 | }; 129 | -------------------------------------------------------------------------------- /src/controllers/error.js: -------------------------------------------------------------------------------- 1 | exports.errorHandler = (err, req, res, next) => { 2 | return res.status(err.status || 500).json({ 3 | error: { 4 | message: err.message || "Oops! Something went wrong.", 5 | }, 6 | }); 7 | }; 8 | -------------------------------------------------------------------------------- /src/controllers/notifications.js: -------------------------------------------------------------------------------- 1 | let notifications = require("../helpers/notifications"); 2 | 3 | exports.findNewSubscriptions = async (req, res, next) => { 4 | try { 5 | let newSubscription = await notifications.findSubscriptionsByUserId( 6 | req.params.id 7 | ); 8 | return res.status(200).json(newSubscription); 9 | } catch (err) { 10 | console.log("findNewSubscriptions - controllers/notifications"); 11 | return next({ 12 | status: 400, 13 | message: err.message, 14 | }); 15 | } 16 | }; 17 | 18 | exports.removeNotification = async (req, res, next) => { 19 | try { 20 | let oldNotification = notifications.remove(req.params.id); 21 | return res.status(200).json(oldNotification); 22 | } catch (err) { 23 | console.log("removeNotification - controllers/notifications"); 24 | return next({ 25 | status: 400, 26 | message: err.message, 27 | }); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/controllers/readings.js: -------------------------------------------------------------------------------- 1 | let Reading = require("../models/reading"), 2 | Tags = require("../models/tags"); 3 | 4 | exports.createReading = async (req, res, next) => { 5 | try { 6 | const reading = await Reading.create(req.body.url, req.params.id); 7 | const reading_id = reading[0].insertId; 8 | if (req.body.tags) 9 | await Tags.create(req.body.tags, reading_id, req.params.id); 10 | return res.status(200).json(reading); 11 | } catch (err) { 12 | console.log("createReading - controllers/readings"); 13 | console.log(err); 14 | return next(err); 15 | } 16 | }; 17 | 18 | exports.summarizeReading = async (req, res, next) => { 19 | try { 20 | let article = await Reading.findById(req.params.id); 21 | let summary = await Reading.summarize(article[0].url); 22 | return res.status(200).json({ 23 | id: req.params.id, 24 | data: summary, 25 | }); 26 | } catch (err) { 27 | console.log("summarizeReading - controllers/readings"); 28 | return next(err); 29 | } 30 | }; 31 | 32 | exports.findAllReadings = async (req, res, next) => { 33 | try { 34 | let allReadings = await Reading.findAll(); 35 | let all = allReadings.map((reading) => { 36 | return createReadingsJSON(reading); 37 | }); 38 | return res.status(200).json(all); 39 | } catch (err) { 40 | console.log("findAllReadings - controllers/readings"); 41 | return next(err); 42 | } 43 | }; 44 | 45 | exports.findUserReadings = async (req, res, next) => { 46 | try { 47 | let userReadings = await Reading.findByUserId(req.params.id); 48 | let user = userReadings.map((reading) => { 49 | return createReadingsJSON(reading); 50 | }); 51 | return res.status(200).json(user); 52 | } catch (err) { 53 | console.log("findUserReadings - controllers/readings"); 54 | return next(err); 55 | } 56 | }; 57 | 58 | exports.findSubscriptionReadings = async (req, res, next) => { 59 | try { 60 | let subReadings = await Reading.findSubReadings(req.params.id); 61 | let sub = subReadings.map((reading) => { 62 | return createReadingsJSON(reading); 63 | }); 64 | return res.status(200).json(sub); 65 | } catch (err) { 66 | console.log("findSubscriptionReadings - controllers/readings"); 67 | return next(err); 68 | } 69 | }; 70 | 71 | exports.findFavoriteReadings = async (req, res, next) => { 72 | try { 73 | let favoriteReadings = await Reading.findFavoriteReadings(req.params.id); 74 | let fav = favoriteReadings.map((reading) => { 75 | return createReadingsJSON(reading); 76 | }); 77 | return res.status(200).json(fav); 78 | } catch (err) { 79 | console.log("findFavoriteReadings - controllers/readings"); 80 | return next(err); 81 | } 82 | }; 83 | 84 | exports.markFavorite = async (req, res, next) => { 85 | try { 86 | let favoritedReading = await Reading.markFavorite( 87 | req.params.id, 88 | req.params.user_id 89 | ); 90 | return res.status(200).json(favoritedReading); 91 | } catch (err) { 92 | console.log("markFavorite - controllers/readings"); 93 | return next(err); 94 | } 95 | }; 96 | 97 | exports.deleteFavorite = async (req, res, next) => { 98 | try { 99 | let favoritedReading = await Reading.deleteFavorite( 100 | req.params.id, 101 | req.params.user_id 102 | ); 103 | return res.status(200).json(favoritedReading); 104 | } catch (err) { 105 | console.log("markFavorite - controllers/readings"); 106 | return next(err); 107 | } 108 | }; 109 | 110 | exports.deleteReading = async (req, res, next) => { 111 | try { 112 | Tags.deleteWithId(req.params.reading_id); 113 | Reading.deleteFavorite(req.params.reading_id, req.params.id); 114 | let deletedReading = await Reading.delete( 115 | req.params.id, 116 | req.params.reading_id 117 | ); 118 | return res.status(200).json(deletedReading); 119 | } catch (err) { 120 | console.log("deleteReading - controllers/readings"); 121 | return next(err); 122 | } 123 | }; 124 | 125 | exports.updateReading = async (req, res, next) => { 126 | try { 127 | // console.log(req.body.url, req.body.user_id, req.params.reading_id) 128 | let reading = await Reading.update( 129 | req.body.url, 130 | req.body.user_id, 131 | req.params.reading_id 132 | ); 133 | return res.status(200).json(reading); 134 | } catch (err) { 135 | console.log("updateReading - controllers/readings"); 136 | return next(err); 137 | } 138 | }; 139 | 140 | const createReadingsJSON = (reading) => { 141 | return { 142 | id: reading.reading_id, 143 | title: reading.title, 144 | domain: reading.domain, 145 | description: reading.description, 146 | reading_image: reading.readings_image, 147 | word_count: reading.word_count, 148 | url: reading.url, 149 | created_at: reading.created_at, 150 | favorite: reading.favorite, 151 | reader: { 152 | id: reading.user_id, 153 | username: reading.username, 154 | image: reading.image, 155 | }, 156 | tags: reading.tag_ids ? reading.tag_ids.split(",") : null, 157 | }; 158 | }; 159 | -------------------------------------------------------------------------------- /src/controllers/search.js: -------------------------------------------------------------------------------- 1 | let Reading = require("../models/reading"), 2 | User = require("../models/user"); 3 | 4 | exports.searchAll = async (req, res, next) => { 5 | try { 6 | let users = await User.findAll(); 7 | let readings = await Reading.findAll(); 8 | return res.status(200).json({ 9 | users, 10 | readings, 11 | }); 12 | } catch (err) { 13 | console.log("searchAll - controllers/search"); 14 | return next(err); 15 | } 16 | }; 17 | 18 | exports.searchUsers = async (req, res, next) => { 19 | try { 20 | let results = await User.findBySearch(req.query.users); 21 | return res.status(200).json(results); 22 | } catch (err) { 23 | console.log("search - controllers/users"); 24 | return next(err); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /src/controllers/subscriptions.js: -------------------------------------------------------------------------------- 1 | let Subscription = require("../models/subscription"); 2 | 3 | exports.createSubscription = async (req, res, next) => { 4 | try { 5 | if (req.params.id !== req.body.sub_id) { 6 | let newSubscription = new Subscription(req.params.id, req.body.sub_id); 7 | let subscription = await Subscription.create(newSubscription); 8 | return res.status(200).json(subscription); 9 | } 10 | } catch (err) { 11 | console.log("createSubscription - controllers/subscriptions"); 12 | if (err.code === "ER_DUP_ENTRY") { 13 | err.message = "You already subscribe to them!"; 14 | } 15 | return next({ 16 | status: 400, 17 | message: err.message, 18 | }); 19 | } 20 | }; 21 | 22 | exports.deleteSubscription = async (req, res, next) => { 23 | try { 24 | let deletedSubscription = await Subscription.delete( 25 | Number(req.params.user_id), 26 | Number(req.params.sub_id) 27 | ); 28 | return res.status(200).json(deletedSubscription); 29 | } catch (err) { 30 | console.log("deleteSubscription - controllers/subscriptions"); 31 | return next(err); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /src/controllers/tags.js: -------------------------------------------------------------------------------- 1 | let Tags = require("../models/tags"); 2 | 3 | exports.createTags = async (req, res, next) => { 4 | try { 5 | const newTag = await Tags.create( 6 | req.body.tags, 7 | req.body.reading_id, 8 | req.params.id 9 | ); 10 | return res.status(200).json(newTag); 11 | } catch (err) { 12 | console.log("createTag - controllers/tags"); 13 | console.log(err); 14 | return next(err); 15 | } 16 | }; 17 | 18 | exports.findAllTags = async (req, res, next) => { 19 | try { 20 | let allTags = await Tags.findAll(); 21 | let tags = allTags.map((tag) => { 22 | return (tag = { 23 | id: tag.id, 24 | tag_name: tag.tag_name, 25 | reading_id: tag.reading_id.split(","), 26 | // 'user_id': tag.user_id.split(','), 27 | date: tag.date, 28 | count: tag.count, 29 | }); 30 | }); 31 | return res.status(200).json(tags); 32 | } catch (err) { 33 | console.log("findTagsByReadingId - controllers/tags"); 34 | console.log(err); 35 | return next(err); 36 | } 37 | }; 38 | 39 | exports.findUserTags = async (req, res, next) => { 40 | try { 41 | let userTags = await Tags.findUserTags(req.params.id); 42 | let tags = userTags.map((tag) => { 43 | return (tag = { 44 | id: tag.id, 45 | tag_name: tag.tag_name, 46 | user_id: tag.user_id.split(","), 47 | date: tag.date, 48 | count: tag.count, 49 | }); 50 | }); 51 | return res.status(200).json(tags); 52 | } catch (err) { 53 | console.log("findUserTags - controllers/tags"); 54 | console.log(err); 55 | return next(err); 56 | } 57 | }; 58 | 59 | exports.findSubscriptionTags = async (req, res, next) => { 60 | try { 61 | let userTags = await Tags.findSubscriptionTags(req.params.id); 62 | let tags = userTags.map((tag) => { 63 | return (tag = { 64 | id: tag.id, 65 | tag_name: tag.tag_name, 66 | // 'reading_id': tag.reading_id.split(','), 67 | user_id: tag.user_id.split(","), 68 | date: tag.date, 69 | count: tag.count, 70 | }); 71 | }); 72 | return res.status(200).json(tags); 73 | } catch (err) { 74 | console.log("findTagsByReadingId - controllers/tags"); 75 | console.log(err); 76 | return next(err); 77 | } 78 | }; 79 | 80 | exports.updateTags = async (req, res, next) => { 81 | try { 82 | // add new tags to tags and reading_tags table 83 | if (req.body.add_tags) { 84 | await Tags.create(req.body.add_tags, req.body.reading_id, req.params.id); 85 | } 86 | 87 | // remove old tags from reading_tags table (keep in tags table) 88 | if (req.body.delete_tags) { 89 | await Tags.deleteFromReading( 90 | req.body.reading_id, 91 | req.body.delete_tags, 92 | req.params.id 93 | ); 94 | } 95 | 96 | return res.status(200).json("Update successful"); 97 | } catch (err) { 98 | console.log("updateTags - controllers/tags"); 99 | console.log(err); 100 | return next(err); 101 | } 102 | }; 103 | 104 | exports.findReadingTags = async (req, res, next) => { 105 | try { 106 | let tags = await Tags.findTagsByReadingId(req.params.id); 107 | return res.status(200).json(tags); 108 | } catch (err) { 109 | console.log("findTagsByReadingId - controllers/tags"); 110 | console.log(err); 111 | return next(err); 112 | } 113 | }; 114 | -------------------------------------------------------------------------------- /src/controllers/users.js: -------------------------------------------------------------------------------- 1 | let User = require("../models/user"); 2 | 3 | exports.findAllUsers = async (req, res, next) => { 4 | try { 5 | let allUsers = await User.findAll(); 6 | return res.status(200).json(allUsers); 7 | } catch (err) { 8 | console.log("findAllUsers - controllers/users"); 9 | return next(err); 10 | } 11 | }; 12 | 13 | exports.findUser = async (req, res, next) => { 14 | try { 15 | let user = await User.findById(req.params.id); 16 | return res.status(200).json(user); 17 | } catch (err) { 18 | console.log("findUser - controllers/users"); 19 | return next(err); 20 | } 21 | }; 22 | 23 | exports.findSubscriptions = async (req, res, next) => { 24 | try { 25 | let following = await User.findSubscriptionsById(req.params.id); 26 | let followers = await User.findFollowersById(req.params.id); 27 | return res.status(200).json({ 28 | following, 29 | followers, 30 | }); 31 | } catch (err) { 32 | console.log("findSubscriptions - controllers/users"); 33 | return next(err); 34 | } 35 | }; 36 | 37 | exports.findFavorites = async (req, res, next) => { 38 | try { 39 | let favorites = await User.findFavorites(req.params.id); 40 | return res.status(200).json(favorites); 41 | } catch (err) { 42 | console.log("favorites - controllers/users"); 43 | return next(err); 44 | } 45 | }; 46 | 47 | exports.updateUser = async (req, res, next) => { 48 | try { 49 | let updated = await User.findByIdAndUpdate( 50 | req.params.id, 51 | req.body.image, 52 | req.body.username 53 | ); 54 | let updatedUser = await User.findById(req.params.id); 55 | return res.status(200).json({ 56 | id: updatedUser[0].id, 57 | username: updatedUser[0].username, 58 | image: updatedUser[0].image, 59 | }); 60 | } catch (err) { 61 | console.log("updateUser - controllers/users"); 62 | return next(err); 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /src/helpers/auth.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | const Datauri = require("datauri"), 3 | path = require("path"), 4 | bcrypt = require("bcrypt"), 5 | jwt = require("jsonwebtoken"), 6 | nodemailer = require("nodemailer"), 7 | cloudinary = require("cloudinary").v2; 8 | 9 | exports.getImage = () => { 10 | const randomIndex = Math.floor(Math.random() * imageUrls.length); 11 | return imageUrls[randomIndex]; 12 | }; 13 | 14 | const imageUrls = [ 15 | "https://res.cloudinary.com/breads/image/upload/v1613539776/naan_mzwzze.jpg", 16 | "https://res.cloudinary.com/breads/image/upload/v1613539707/breadsticks_tzpz9b.jpg", 17 | "https://res.cloudinary.com/breads/image/upload/v1613539680/focaccia_jasnlz.jpg", 18 | "https://res.cloudinary.com/breads/image/upload/v1613539636/pita_aqpuld.jpg", 19 | "https://res.cloudinary.com/breads/image/upload/v1613539580/tortilla_fvnmgn.jpg", 20 | "https://res.cloudinary.com/breads/image/upload/v1613539536/sourdough_kb4mt4.jpg", 21 | "https://res.cloudinary.com/breads/image/upload/v1613539497/baguette_sa1wgi.jpg", 22 | "https://res.cloudinary.com/breads/image/upload/v1613539400/crumpet_rlznki.jpg", 23 | "https://res.cloudinary.com/breads/image/upload/v1613539360/ciabatta_y34dzx.jpg", 24 | ]; 25 | 26 | /** 27 | * @description This function converts the buffer to data url 28 | * @param {Object} req.file containing the field object 29 | * @returns {String} The data url from the string buffer 30 | */ 31 | 32 | exports.dataUri = (file) => { 33 | const dUri = new Datauri(); 34 | return dUri.format( 35 | path.extname(file.originalname).toString(), 36 | req.file.buffer 37 | ); 38 | }; 39 | 40 | exports.generateHash = (password) => { 41 | const salt = bcrypt.genSaltSync(10); 42 | const hash = bcrypt.hashSync(password, salt); 43 | return hash; 44 | }; 45 | 46 | exports.createToken = (id, username, image) => { 47 | return jwt.sign( 48 | { 49 | id, 50 | username, 51 | image, 52 | }, 53 | process.env.SECRET_KEY 54 | ); 55 | }; 56 | 57 | exports.comparePassword = (candidatePassword, password, next) => { 58 | bcrypt.compare(candidatePassword, password, (err, isMatch) => { 59 | if (err) return next(err); 60 | next(null, isMatch); 61 | }); 62 | }; 63 | 64 | exports.isRealUser = (user, token) => { 65 | const secret = user.password + "-" + user.createdAt; 66 | const payload = jwt.decode(token, secret); 67 | return payload.id === user.id; 68 | }; 69 | 70 | exports.getEmailToken = ({ id, password, created_at }) => { 71 | const secret = password + "-" + created_at; 72 | const token = jwt.sign({ id }, secret, { 73 | expiresIn: 3600, // 1 hour 74 | }); 75 | return token; 76 | }; 77 | 78 | exports.getPasswordResetURL = (username, token) => 79 | `${process.env.EMAIL_URL}/reset/${username}/${token}`; 80 | 81 | exports.emailTemplate = (email, name, url) => { 82 | const from = process.env.EMAIL_LOGIN; 83 | const to = email; 84 | const subject = "🍞 Breads Password Reset 🍞"; 85 | const html = ` 86 |

Hey ${name || email},

87 |

We heard that you lost your Breads password. Sorry about that!

88 |

But don’t worry! You can use the following link to reset your password:

89 | ${url} 90 |

If you don’t use this link within 1 hour, it will expire.

91 |

Read something fun today!

92 |

–Your friends at Breads

93 | `; 94 | return { from, to, subject, html }; 95 | }; 96 | 97 | exports.sendEmail = (emailTemplate, next) => { 98 | transporter.sendMail(emailTemplate, (err, info) => { 99 | if (err) { 100 | console.log("helpers/auth - sendEmail"); 101 | console.log(err); 102 | return next({ 103 | status: 500, 104 | message: "Error sending email", 105 | }); 106 | } 107 | console.log(`** Email sent **`, info.response); 108 | return "Reset password email sent. Please check your inbox."; 109 | }); 110 | }; 111 | 112 | const transporter = nodemailer.createTransport({ 113 | service: "gmail", 114 | auth: { 115 | user: process.env.EMAIL_LOGIN, 116 | pass: process.env.EMAIL_PASSWORD, 117 | }, 118 | }); 119 | 120 | exports.imageUpload = (file) => { 121 | const imageUpload = new Promise((resolve, reject) => { 122 | cloudinary.uploader.upload( 123 | file, 124 | { resource_type: "auto" }, 125 | function (err, result) { 126 | if (err) reject(err); 127 | else resolve({ url: result.secure_url, id: result.public_id }); 128 | } 129 | ); 130 | }); 131 | return imageUpload; 132 | }; 133 | -------------------------------------------------------------------------------- /src/helpers/notifications.js: -------------------------------------------------------------------------------- 1 | let db = require("../models"); 2 | 3 | exports.findSubscriptionsByUserId = (user_id) => { 4 | let newSubscriber = new Promise((resolve, reject) => { 5 | db.connection.query( 6 | "SELECT username, publisher_id, subscriber_id FROM users INNER JOIN subscriptions ON subscriber_id = users.id WHERE isNew = 1 AND publisher_id = ?", 7 | user_id, 8 | function (err, results) { 9 | if (err) reject(err); 10 | else resolve(results); 11 | } 12 | ); 13 | }); 14 | return newSubscriber; 15 | }; 16 | 17 | exports.remove = (user_id) => { 18 | let oldNotification = new Promise((resolve, reject) => { 19 | db.connection.query( 20 | "UPDATE subscriptions SET isNew = 0 WHERE publisher_id = ?", 21 | user_id, 22 | function (err, results) { 23 | if (err) reject(err); 24 | else resolve(results); 25 | } 26 | ); 27 | }); 28 | return oldNotification; 29 | }; 30 | -------------------------------------------------------------------------------- /src/middleware/auth.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | const jwt = require("jsonwebtoken"), 3 | multer = require("multer"), 4 | cloudinary = require("cloudinary").v2; 5 | 6 | exports.loginRequired = function (req, res, next) { 7 | try { 8 | let token = req.headers.authorization.split(" ")[1]; 9 | jwt.verify(token, process.env.SECRET_KEY, function (err, decoded) { 10 | if (decoded) return next(); 11 | else 12 | next({ 13 | status: 401, 14 | message: "Please log in first", 15 | }); 16 | }); 17 | } catch (err) { 18 | next({ 19 | status: 401, 20 | message: "Please log in first", 21 | }); 22 | } 23 | }; 24 | 25 | exports.ensureCorrectUser = function (req, res, next) { 26 | try { 27 | let token = req.headers.authorization.split(" ")[1]; 28 | jwt.verify(token, process.env.SECRET_KEY, function (err, decoded) { 29 | if (decoded && decoded.id == req.params.id) return next(); 30 | else 31 | next({ 32 | status: 401, 33 | message: "Unauthorized", 34 | }); 35 | }); 36 | } catch (err) { 37 | next({ 38 | status: 401, 39 | message: "Unauthorized", 40 | }); 41 | } 42 | }; 43 | 44 | const storage = multer.memoryStorage(); 45 | 46 | const imageFilter = (req, file, cb) => { 47 | // accept image files only 48 | if (!file.originalname.match(/\.(jpg|jpeg|png|gif)$/i)) { 49 | return cb(new Error("Only image files are allowed!"), false); 50 | } 51 | cb(null, true); 52 | }; 53 | 54 | exports.handleFormData = multer({ storage, fileFilter: imageFilter }); 55 | 56 | exports.cloudinaryConfig = (req, res, next) => { 57 | cloudinary.config({ 58 | cloud_name: process.env.CLOUDINARY_CLOUD_NAME, 59 | api_key: process.env.CLOUDINARY_API_KEY, 60 | api_secret: process.env.CLOUDINARY_API_SECRET, 61 | }); 62 | next(); 63 | }; 64 | -------------------------------------------------------------------------------- /src/models/index.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | const mysql = require("mysql2"); 3 | 4 | const localSetup = { 5 | connectionLimit: 25, 6 | host: process.env.HOST || process.env.LOCAL_HOST, 7 | user: process.env.USERNAME || process.env.LOCAL_USER, 8 | password: process.env.DBPASSWORD || process.env.LOCAL_DBPASSWORD, 9 | database: process.env.DB || process.env.LOCAL_DB, 10 | }; 11 | 12 | const connection = mysql.createPool(localSetup); 13 | 14 | module.exports.connection = connection; 15 | -------------------------------------------------------------------------------- /src/models/reading.js: -------------------------------------------------------------------------------- 1 | let db = require("."), 2 | { PythonShell } = require("python-shell"), 3 | queries = require("../queries/reading"); 4 | 5 | class Reading { 6 | constructor(id, title, domain, description, image, word_count, url, user_id) { 7 | (this.id = id), 8 | (this.title = title), 9 | (this.domain = domain), 10 | (this.description = description), 11 | (this.image = image), 12 | (this.word_count = word_count), 13 | (this.url = url), 14 | (this.user_id = user_id); 15 | } 16 | 17 | static async create(url, user_id) { 18 | let options = { args: [url, user_id] }; 19 | let parseReading = await new Promise((resolve, reject) => { 20 | PythonShell.run("src/reading_scraper.py", options, (err, data) => { 21 | if (err) { 22 | // console.log(err) 23 | reject(err); 24 | } 25 | // console.log(data) 26 | return resolve(data); 27 | }); 28 | }); 29 | // console.log(parseReading) 30 | const values = JSON.parse(parseReading[0]); 31 | console.log("CREATE", values); 32 | const insertReading = await new Promise((resolve, reject) => { 33 | db.connection.query(queries.insertReading, [[values]], (err, results) => { 34 | if (err) reject(err); 35 | else resolve(results); 36 | }); 37 | }); 38 | 39 | const reading_id = insertReading.insertId; 40 | const insertUserReading = await new Promise((resolve, reject) => { 41 | db.connection.query( 42 | queries.insertUserReading, 43 | [parseInt(user_id), reading_id], 44 | (err, results) => { 45 | if (err) reject(err); 46 | else resolve(results); 47 | } 48 | ); 49 | }); 50 | 51 | return [insertReading, insertUserReading]; 52 | } 53 | 54 | static findById(id) { 55 | let reading = new Promise(function (resolve, reject) { 56 | db.connection.query( 57 | queries.selectReadingById, 58 | id, 59 | function (err, results) { 60 | if (err) reject(err); 61 | else resolve(results); 62 | } 63 | ); 64 | }); 65 | return reading; 66 | } 67 | 68 | static summarize(url) { 69 | let options = { args: [url] }; 70 | let summary = new Promise((resolve, reject) => { 71 | PythonShell.run("reading_summary.py", options, function (err, data) { 72 | if (err) reject(err); 73 | return resolve(data); 74 | }); 75 | }); 76 | return summary; 77 | } 78 | 79 | static findAll() { 80 | let readings = new Promise(function (resolve, reject) { 81 | db.connection.query(queries.selectAllReadings, function (err, results) { 82 | if (err) reject(err); 83 | else resolve(results); 84 | }); 85 | }); 86 | return readings; 87 | } 88 | 89 | static findByUserId(id) { 90 | let userReadings = new Promise(function (resolve, reject) { 91 | db.connection.query( 92 | queries.selectUserReadings, 93 | id, 94 | function (err, results) { 95 | if (err) reject(err); 96 | return resolve(results); 97 | } 98 | ); 99 | }); 100 | return userReadings; 101 | } 102 | 103 | static findSubReadings(sub_id) { 104 | let subscriptionReadings = new Promise((resolve, reject) => { 105 | db.connection.query( 106 | queries.selectSubscriptionReadings, 107 | sub_id, 108 | function (err, results) { 109 | if (err) reject(err); 110 | else resolve(results); 111 | } 112 | ); 113 | }); 114 | return subscriptionReadings; 115 | } 116 | 117 | static findFavoriteReadings(id) { 118 | let favoriteReadings = new Promise((resolve, reject) => { 119 | db.connection.query( 120 | queries.selectFavoriteReadings, 121 | id, 122 | function (err, results) { 123 | if (err) reject(err); 124 | return resolve(results); 125 | } 126 | ); 127 | }); 128 | return favoriteReadings; 129 | } 130 | 131 | static markFavorite(reading_id, user_id) { 132 | let favorite = new Promise((resolve, reject) => { 133 | db.connection.query( 134 | queries.updateFavorite, 135 | [user_id, reading_id], 136 | function (err, results) { 137 | if (err) reject(err); 138 | else resolve(results); 139 | } 140 | ); 141 | }); 142 | return favorite; 143 | } 144 | 145 | static deleteFavorite(id, user_id) { 146 | let favorite = new Promise((resolve, reject) => { 147 | db.connection.query( 148 | queries.deleteFavorite, 149 | [user_id, id], 150 | function (err, results) { 151 | if (err) reject(err); 152 | else resolve(results); 153 | } 154 | ); 155 | }); 156 | return favorite; 157 | } 158 | 159 | static delete(user_id, reading_id) { 160 | let deletedReading = new Promise((resolve, reject) => { 161 | db.connection.query( 162 | queries.deleteReading, 163 | [user_id, reading_id], 164 | function (err, results) { 165 | if (err) reject(err); 166 | else resolve(results); 167 | } 168 | ); 169 | }); 170 | return deletedReading; 171 | } 172 | 173 | static update(url, user_id, reading_id) { 174 | let options = { args: [url, user_id] }; 175 | let reading = new Promise((resolve, reject) => { 176 | PythonShell.run("src/reading_scraper.py", options, function (err, data) { 177 | if (err) reject(err); 178 | return resolve(data); 179 | }); 180 | }); 181 | 182 | let updateResult = reading.then((data) => { 183 | let values = JSON.parse(data[0]); 184 | console.log(values); 185 | let query = new Promise(function (resolve, reject) { 186 | db.connection.query( 187 | queries.updateReading, 188 | [values, reading_id], 189 | function (err, results) { 190 | if (err) reject(err); 191 | else resolve(results); 192 | } 193 | ); 194 | }); 195 | return query; 196 | }); 197 | 198 | return updateResult; 199 | } 200 | 201 | static findIdByUrl(url) { 202 | let id = new Promise((resolve, reject) => { 203 | db.connection.query( 204 | queries.selectReadingIdByUrl, 205 | [url], 206 | function (err, results) { 207 | if (err) reject(err); 208 | else resolve(results); 209 | } 210 | ); 211 | }); 212 | return id; 213 | } 214 | } 215 | 216 | module.exports = Reading; 217 | -------------------------------------------------------------------------------- /src/models/subscription.js: -------------------------------------------------------------------------------- 1 | let db = require("."), 2 | queries = require("../queries/subscription"); 3 | 4 | class Subscription { 5 | constructor(subscriber_id, publisher_id) { 6 | (this.subscriber_id = subscriber_id), (this.publisher_id = publisher_id); 7 | } 8 | 9 | static create(user_id, sub_id) { 10 | let subscription = new Promise((resolve, reject) => { 11 | db.connection.query( 12 | queries.insertSubscription, 13 | [user_id, sub_id], 14 | (err, results) => { 15 | if (err) reject(err); 16 | else resolve(results); 17 | } 18 | ); 19 | }); 20 | return subscription; 21 | } 22 | 23 | static findBySubId(sub_id) { 24 | let subscription = new Promise((resolve, reject) => { 25 | db.connection.query( 26 | queries.selectSubscriptionById, 27 | sub_id, 28 | (err, results) => { 29 | if (err) reject(err); 30 | else resolve(results); 31 | } 32 | ); 33 | }); 34 | return subscription; 35 | } 36 | 37 | static delete(user_id, sub_id) { 38 | let subscription = new Promise((resolve, reject) => { 39 | db.connection.query( 40 | queries.deleteSubscription, 41 | [user_id, sub_id], 42 | (err, results) => { 43 | if (err) reject(err); 44 | else resolve(results); 45 | } 46 | ); 47 | }); 48 | return subscription; 49 | } 50 | } 51 | 52 | module.exports = Subscription; 53 | -------------------------------------------------------------------------------- /src/models/tags.js: -------------------------------------------------------------------------------- 1 | let db = require("."), 2 | Reading = require("./reading"), 3 | queries = require("../queries/tag"); 4 | 5 | class Tags { 6 | constructor(url, tags, user_id) { 7 | this.url = url; 8 | this.tags = tags; 9 | this.user_id = user_id; 10 | } 11 | 12 | static async create(tags, reading_id, user_id) { 13 | const tagsArray = tags 14 | .split("#") 15 | .filter((tag) => tag !== "") 16 | .map((tag) => [tag.trim()]); 17 | const createdTags = await new Promise(function (resolve, reject) { 18 | db.connection.query(queries.insertTags, [tagsArray], (err, results) => { 19 | if (err) reject(err); 20 | else resolve(results); 21 | }); 22 | }); 23 | 24 | // const readingId = await Reading.findIdByUrl(url); 25 | const tagIds = await Promise.all( 26 | tagsArray.map(async (tag) => await this.findIdByTagName(tag)) 27 | ); 28 | 29 | const readingTags = tagIds.map((tag_id) => [reading_id, tag_id[0].id]); 30 | const insertReadingTags = await new Promise((resolve, reject) => { 31 | db.connection.query( 32 | queries.insertReadingTags, 33 | [readingTags], 34 | (err, results) => { 35 | if (err) reject(err); 36 | else resolve(results); 37 | } 38 | ); 39 | }); 40 | 41 | const userTags = tagIds.map((tag_id) => [parseInt(user_id), tag_id[0].id]); 42 | const insertUserTags = await new Promise((resolve, reject) => { 43 | db.connection.query( 44 | queries.insertUserTags, 45 | [userTags], 46 | (err, results) => { 47 | if (err) reject(err); 48 | else resolve(results); 49 | } 50 | ); 51 | }); 52 | 53 | return [createdTags, insertReadingTags, insertUserTags]; 54 | } 55 | 56 | static findIdByTagName(tag) { 57 | let id = new Promise((resolve, reject) => { 58 | db.connection.query(queries.selectIdByTagName, tag, (err, results) => { 59 | if (err) reject(err); 60 | else resolve(results); 61 | }); 62 | }); 63 | return id; 64 | } 65 | 66 | static findAll() { 67 | let tags = new Promise((resolve, reject) => { 68 | db.connection.query(queries.selectAllTags, (err, results) => { 69 | if (err) reject(err); 70 | else resolve(results); 71 | }); 72 | }); 73 | return tags; 74 | } 75 | 76 | static findUserTags(user_id) { 77 | let tags = new Promise((resolve, reject) => { 78 | db.connection.query(queries.selectUserTags, user_id, (err, results) => { 79 | if (err) reject(err); 80 | else resolve(results); 81 | }); 82 | }); 83 | return tags; 84 | } 85 | 86 | static findSubscriptionTags(user_id) { 87 | let tags = new Promise((resolve, reject) => { 88 | db.connection.query( 89 | queries.selectSubscriptionTags, 90 | user_id, 91 | (err, results) => { 92 | if (err) reject(err); 93 | else resolve(results); 94 | } 95 | ); 96 | }); 97 | return tags; 98 | } 99 | 100 | static deleteWithId(reading_id) { 101 | const deletedTags = new Promise((resolve, reject) => { 102 | db.connection.query(queries.deleteTags, reading_id, (err, results) => { 103 | if (err) reject(err); 104 | else resolve(results); 105 | }); 106 | }); 107 | 108 | return deletedTags; 109 | } 110 | 111 | static async deleteFromReading(reading_id, tags, user_id) { 112 | const tagsArray = tags 113 | .split("#") 114 | .filter((tag) => tag !== "") 115 | .map((tag) => [tag.trim()]); 116 | const tagIds = await Promise.all( 117 | tagsArray.map(async (tag) => await this.findIdByTagName(tag)) 118 | ); 119 | 120 | const readingTags = tagIds.map((tag_id) => [reading_id, tag_id[0].id]); 121 | const deletedReadingTags = new Promise((resolve, reject) => { 122 | db.connection.query( 123 | queries.deleteReadingTags, 124 | [[readingTags]], 125 | (err, results) => { 126 | if (err) reject(err); 127 | else resolve(results); 128 | } 129 | ); 130 | }); 131 | 132 | const userTags = tagIds.map((tag_id) => [parseInt(user_id), tag_id[0].id]); 133 | const deletedUserTags = new Promise((resolve, reject) => { 134 | db.connection.query( 135 | queries.deleteUserTags, 136 | [[userTags]], 137 | (err, results) => { 138 | if (err) reject(err); 139 | else resolve(results); 140 | } 141 | ); 142 | }); 143 | 144 | return [deletedReadingTags, deletedUserTags]; 145 | } 146 | } 147 | 148 | module.exports = Tags; 149 | -------------------------------------------------------------------------------- /src/models/user.js: -------------------------------------------------------------------------------- 1 | const db = require("."), 2 | queries = require("../queries/user"); 3 | 4 | exports.create = (user) => { 5 | const newUser = new Promise((resolve, reject) => { 6 | db.connection.query(queries.insertUser, [[user]], (err, results) => { 7 | if (err) reject(err); 8 | else resolve(results); 9 | }); 10 | }); 11 | return newUser; 12 | }; 13 | 14 | exports.findByUsername = (username) => { 15 | const user = new Promise((resolve, reject) => { 16 | db.connection.query(queries.selectByUsername, username, (err, results) => { 17 | if (err) reject(err); 18 | else resolve(results); 19 | }); 20 | }); 21 | return user; 22 | }; 23 | 24 | exports.findById = (id) => { 25 | const user = new Promise((resolve, reject) => { 26 | db.connection.query(queries.selectById, id, (err, results) => { 27 | if (err) reject(err); 28 | else resolve(results); 29 | }); 30 | }); 31 | return user; 32 | }; 33 | 34 | exports.findByEmail = (email) => { 35 | const user = new Promise((resolve, reject) => { 36 | db.connection.query(queries.selectByEmail, email, (err, results) => { 37 | if (err) reject(err); 38 | else resolve(results); 39 | }); 40 | }); 41 | return user; 42 | }; 43 | 44 | exports.delete = (username) => { 45 | const user = new Promise((resolve, reject) => { 46 | db.connection.query(queries.deleteUser, username, (err, results) => { 47 | if (err) reject(err); 48 | else resolve(results); 49 | }); 50 | }); 51 | return user; 52 | }; 53 | 54 | exports.findAll = () => { 55 | const users = new Promise((resolve, reject) => { 56 | db.connection.query(queries.selectAllUsers, (err, results) => { 57 | if (err) reject(err); 58 | else resolve(results); 59 | }); 60 | }); 61 | return users; 62 | }; 63 | 64 | exports.findSubscriptionsById = (sub_id) => { 65 | const subscriptions = new Promise((resolve, reject) => { 66 | db.connection.query( 67 | queries.selectUserSubscriptions, 68 | sub_id, 69 | (err, results) => { 70 | if (err) reject(err); 71 | else resolve(results); 72 | } 73 | ); 74 | }); 75 | return subscriptions; 76 | }; 77 | 78 | exports.findFollowersById = (id) => { 79 | const followers = new Promise((resolve, reject) => { 80 | db.connection.query(queries.selectUserSubscribers, id, (err, results) => { 81 | if (err) reject(err); 82 | else resolve(results); 83 | }); 84 | }); 85 | return followers; 86 | }; 87 | 88 | exports.findBySearch = (string) => { 89 | const result = new Promise((resolve, reject) => { 90 | db.connection.query( 91 | queries.searchUsers, 92 | [string, string], 93 | (err, results) => { 94 | if (err) reject(err); 95 | else resolve(results); 96 | } 97 | ); 98 | }); 99 | return result; 100 | }; 101 | 102 | exports.findByIdAndUpdate = (id, image, username) => { 103 | const updatedUser = new Promise((resolve, reject) => { 104 | db.connection.query( 105 | queries.updateUser, 106 | [image, username, id], 107 | (err, results) => { 108 | if (err) reject(err); 109 | else resolve(results); 110 | } 111 | ); 112 | }); 113 | return updatedUser; 114 | }; 115 | 116 | exports.findByIdAndUpdatePassword = (password, id) => { 117 | const updatedUser = new Promise((resolve, reject) => { 118 | db.connection.query( 119 | queries.updateUserPassword, 120 | [password, id], 121 | (err, results) => { 122 | if (err) reject(err); 123 | else resolve(results); 124 | } 125 | ); 126 | }); 127 | return updatedUser; 128 | }; 129 | 130 | exports.findFavorites = (id) => { 131 | const favorites = new Promise((resolve, reject) => { 132 | db.connection.query(queries.selectUserFavorites, id, (err, results) => { 133 | if (err) reject(err); 134 | else resolve(results); 135 | }); 136 | }); 137 | return favorites; 138 | }; 139 | -------------------------------------------------------------------------------- /src/mysql/import.sql: -------------------------------------------------------------------------------- 1 | -- USERS 2 | INSERT INTO users ( 3 | first_name, 4 | last_name, 5 | email, 6 | username, 7 | password, 8 | image, 9 | created_at 10 | ) VALUES 11 | ('First','User','first@user.com','firstUser','$2b$10$6Hlgf70PI6lZT5ZP7RRNQuLfRcG3wx.PsAcOu983TgFEp/bhDYKwa','https://res.cloudinary.com/breads/image/upload/v1613539776/naan_mzwzze.jpg','2020-03-21 14:54:19'), 12 | ('Second','User','second@user.com','secondUser','$2b$10$6Hlgf70PI6lZT5ZP7RRNQuLfRcG3wx.PsAcOu983TgFEp/bhDYKwa','https://res.cloudinary.com/breads/image/upload/v1613539680/focaccia_jasnlz.jpg','2020-03-21 18:39:00'), 13 | ('Third','User','third@user.com','thirdUser','$2b$10$6Hlgf70PI6lZT5ZP7RRNQuLfRcG3wx.PsAcOu983TgFEp/bhDYKwa','https://res.cloudinary.com/breads/image/upload/v1613539497/baguette_sa1wgi.jpg','2020-03-23 18:23:03'), 14 | ('Fourth','User','fourth@user.com','fourthUser','$2b$10$6Hlgf70PI6lZT5ZP7RRNQuLfRcG3wx.PsAcOu983TgFEp/bhDYKwa','https://res.cloudinary.com/breads/image/upload/v1613539536/sourdough_kb4mt4.jpg','2020-03-27 11:33:27'), 15 | ('Fifth','User','fifth@user.com','fifthUser','$2b$10$6Hlgf70PI6lZT5ZP7RRNQuLfRcG3wx.PsAcOu983TgFEp/bhDYKwa','https://res.cloudinary.com/breads/image/upload/v1613539580/tortilla_fvnmgn.jpg','2020-03-27 11:46:36'); 16 | 17 | -- READINGS 18 | INSERT INTO readings ( 19 | title, 20 | domain, 21 | description, 22 | image, 23 | word_count, 24 | url, 25 | created_at 26 | ) VALUES 27 | ('Learning How to Think: The Skill No One Taught You','fs.blog',"One of the best skills you can learn is how to think for your self. Only we've never been taught how to think. Read this to learn how to think better.",'https://149366099.v2.pressablecdn.com/wp-content/uploads/2015/08/William-Deresiewicz-Thinking.png',851,'https://fs.blog/2015/08/how-to-think/',"2020-03-21 18:42:33"), 28 | ('How a Decision Journal Changed the Way I make Decisions (Template Included)','fs.blog','Read this article to learn how a decision journal (template included) can improve the way you make decisions by giving you a critical feedback loop.','https://149366099.v2.pressablecdn.com/wp-content/uploads/2014/02/decision-Journal.png',1260,'https://fs.blog/2014/02/decision-journal/',"2020-03-22 13:51:01"), 29 | ('A Home Maintenance Checklist: An Incredibly Handy Tool to Keep Your House in Tip-Top Shape | The Art of Manliness','www.artofmanliness.com',"A home maintenance checklist that's incredibly handy and will keep your house in tip-top shape. Organized annually, biannually, quarterly, and seasonally.",'https://content.artofmanliness.com/uploads/2013/10/Screen-Shot-2019-01-27-at-7.50.00-PM.png',2396,'https://www.artofmanliness.com/articles/keep-your-house-in-tip-top-shape-an-incredibly-handy-home-maintenance-checklist/',"2020-03-23 18:23:35"), 30 | ('The Best way to Improve Your Performance at Almost Anything','fs.blog','','',10991,'https://fs.blog/2014/05/improving-your-performance/',"2020-03-23 23:38:27"), 31 | ('Why Men Should Read Fiction | The Art of Manliness','www.artofmanliness.com','Learn how reading fiction will help you become a better man.','https://content.artofmanliness.com/uploads//2012/04/manreading.jpg',2182,'https://www.artofmanliness.com/articles/why-men-should-read-more-fiction/',"2020-03-25 16:35:15"), 32 | ('How to Create a Life Plan in 5 Easy Steps | The Art of Manliness','www.artofmanliness.com','Create a plan for the life you want in just five simple steps.','https://content.artofmanliness.com/uploads/2011/02/blueprint-1.jpg',2554,'https://www.artofmanliness.com/articles/create-a-life-plan/',"2020-03-27 11:17:33"), 33 | ("Richard Feynman’s Love Letter to His Wife, Sixteen Months After Her Death",'fs.blog','','',4007,'https://fs.blog/2013/08/richard-feynman-love-letter/',"2020-03-27 11:19:53"), 34 | ('From “Simple” to Serious Endurance','www.strongfirst.com','','',17892,'https://www.strongfirst.com/where-do-you-go-after-simple/',"2020-03-27 11:21:27"), 35 | ('What the Most Successful People Do Before Breakfast','fs.blog','','',10690,'https://fs.blog/2014/01/what-the-most-successful-people-do-before-breakfast/',"2020-03-27 11:22:55"), 36 | ('"The 3 steps I teach trainers and health coaches to help fix any diet problem. A proven formula for helping people look and, "feel their best".','www.precisionnutrition.com','','',16085,'https://www.precisionnutrition.com/fix-any-diet-problem',"2020-03-27 11:44:37"), 37 | ('Teimour Radjabov: "I should consult a lawyer"','www.chess.com','','',30188,'https://www.chess.com/news/view/teimour-radjabov-interview-fide-candidates-chess',"2020-03-27 12:06:08"), 38 | ('Programs','www.oaklandsmostpowerful.com','','',2322,'https://www.oaklandsmostpowerful.com/programming-resources',"2020-03-27 20:31:28"), 39 | ('What is “Work Capacity”? [Part I]','www.strongfirst.com','','',19915,'https://www.strongfirst.com/work-capacity-part-i/',"2020-03-27 20:33:10"), 40 | ('My Journey to the Beast: Pressing a 48kg Kettlebell','www.strongfirst.com','','',10179,'https://www.strongfirst.com/my-journey-to-the-beast-pressing-a-48kg-kettlebell/',"2020-03-27 20:34:44"), 41 | ('Willpower: How to Strengthen and Conserve Your Willpower | The Art of Manliness','www.artofmanliness.com','Strengthen and conserve your willpower with these 20 tips.','https://content.artofmanliness.com/uploads//2012/01/gas.jpg',3270,'https://www.artofmanliness.com/articles/how-to-strengthen-willpower/',"2020-03-27 20:37:14"), 42 | ('The Mechanics of the War Economy','www.linkedin.com','What is happening now would have been unimaginable not long ago.  It was only unimaginable because it had never happened in our lifetimes.','https://media-exp1.licdn.com/dms/image/C4D12AQGBrORFqUfwEA/article-cover_image-shrink_600_2000/0?e=1602720000&v=beta&t=pSeyu4WWv3KetXYucJTqoaR65jAtDZaYSV9xlaAI6R0',5916,'https://www.linkedin.com/pulse/mechanics-war-economy-ray-dalio/?trackingId=yB1ZbrXdSgGRvc%2FAq3m%2FIQ%3D%3D',"2020-03-29 18:14:53"), 43 | ('Our Disembodied Selves and the Decline of Empathy','www.artofmanliness.com','','',14526,'https://www.artofmanliness.com/articles/our-disembodied-selves-and-the-decline-of-empathy/',"2020-03-30 11:23:48"), 44 | ('How to Implement Infinite Scrolling with ReactJs','www.pluralsight.com','','',6036,'https://www.pluralsight.com/guides/how-to-implement-infinite-scrolling-with-reactjs',"2020-03-30 23:03:22"), 45 | ('How to Use the Python or Operator','realpython.com','','',19777,'https://realpython.com/python-or-operator/',"2020-04-22 23:32:16"), 46 | ('From Simple to Sinister: Waving Volume on S&S','www.strongfirst.com','','',12592,'https://www.strongfirst.com/from-simple-to-sinister/',"2020-04-25 13:23:54"), 47 | ('"Things Are Going So Well, Help Me Screw It Up, Part 2"','www.strongfirst.com','','',13542,'https://www.strongfirst.com/things-are-going-so-well-help-me-screw-it-up-part-2/',"2020-04-25 13:24:19"), 48 | ("bvaughn / react-virtualized",'github.com','','',3081,'https://github.com/bvaughn/react-virtualized/issues/1378',"2020-04-29 14:05:35"), 49 | ('Optimizing Performance','reactjs.org','','',9027,'https://reactjs.org/docs/optimizing-performance.html',"2020-04-29 14:40:35"), 50 | ("Chaining async await API calls in Redux",'dev.to','','',1300,'https://dev.to/sanderdebr/chaining-async-await-api-calls-in-redux-2m96',"2020-04-30 23:20:46"), 51 | ('How to dispatch a Redux action with a timeout?','stackoverflow.com','','',15620,'https://stackoverflow.com/questions/35411423/how-to-dispatch-a-redux-action-with-a-timeout',"2020-04-30 23:24:39"), 52 | ('#Usage with React','redux.js.org','','',6996,'https://redux.js.org/basics/usage-with-react','2020-04-30 23:35:40'), 53 | ('The complete guide to Forms in React','medium.com','','',6527,'https://medium.com/@agoiabeladeyemi/the-complete-guide-to-forms-in-react-d2ba93f32825','2020-04-30 23:36:41'), 54 | ('Why logic gates behave the way they do?','cs.stackexchange.com','','',3001,'https://cs.stackexchange.com/questions/125000/why-logic-gates-behave-the-way-they-do','2020-04-30 23:40:18'), 55 | ('How to Be a Man | The Art of Manliness','www.artofmanliness.com',"How to be a man in the 21st century, based on years of reading, research, and experience. Included are principles and actions that will make you a better man.",'https://content.artofmanliness.com/uploads/2014/06/road.png',19967,'https://www.artofmanliness.com/articles/semper-virilis-a-roadmap-to-manhood-in-the-21st-century/#chapter2','2020-04-30 23:42:22'), 56 | ("To Succeed in Work and Life, Be a T-Shaped Man | The Art of Manliness",'www.artofmanliness.com',"A well-rounded life is T-shaped; he has expertise in one specific area, and a broad knowledge across multiple disciplines.",'https://content.artofmanliness.com/uploads//2013/04/mrtx.jpg',1787,'https://www.artofmanliness.com/articles/to-succeed-in-work-and-life-be-mr-t/','2020-05-01 0:12:41'), 57 | ('Why do logic gates behave the way they do?','cs.stackexchange.com',"I am a Software Developer but I came from a non-CS background so maybe it is a wrong question to ask, but I do not get why logic gates/boolean logic behave the way they do. Why for example: 1 A...",'https://cdn.sstatic.net/Sites/cs/Img/apple-touch-icon@2.png?v=324a3e0c2b03',28,'https://cs.stackexchange.com/questions/125000/why-logic-gates-behave-the-way-they-do','2020-05-01 13:05:45'), 58 | ('Uploading Images to Cloudinary Using Multer and ExpressJS','medium.com','','',6790,'https://medium.com/@joeokpus/uploading-images-to-cloudinary-using-multer-and-expressjs-f0b9a4e14c54','2020-05-02 19:43:56'), 59 | ('Coronavirus Live Updates: Trump Administration Models Predict Near Doubling of Daily Death Toll by June','www.nytimes.com','','',36240,'https://www.nytimes.com/2020/05/04/us/coronavirus-updates.html','2020-05-04 12:55:08'), 60 | ('Trump and Biden Would Tie in Texas if Presidential Election Were Held Today: Poll','www.newsweek.com','','',2880,'https://www.newsweek.com/biden-trump-would-tie-election-texas-1501639','2020-05-04 15:11:14'), 61 | ("LeBron: My style would've jelled with Jordan's",'www.espn.com','','',646,'https://www.espn.com/nba/story/_/id/29192329/lebron-james-mulls-role-mj-teammate-my-best-assets-work-perfectly','2020-05-18 23:23:57'), 62 | ('Zoom Acquires Keybase and Announces Goal of Developing the Most Broadly Used Enterprise End-to-End Encryption Offering','blog.zoom.us','','',1094,'https://blog.zoom.us/wordpress/2020/05/07/zoom-acquires-keybase-and-announces-goal-of-developing-the-most-broadly-used-enterprise-end-to-end-encryption-offering/','2020-05-18 23:27:29'), 63 | ("Americans don't trust the Federal Reserve to look out for them amid coronavirus pandemic",'www.axios.com','','',447,'https://www.axios.com/federal-reserve-trust-americans-coronavirus-0ce40d39-c39c-4a7f-bc42-22f75af69772.html','2020-05-18 23:27:51'), 64 | ("Why flats dominate Spain’s housing market",'www.bbc.com','','',348,'https://www.bbc.com/worklife/article/20200506-why-do-flats-dominate-spains-housing-market','2020-05-18 23:28:33'), 65 | ("Commentary on the Epistle to The Galatians, by Martin Luther",'www.gutenberg.org','','',80116,'https://www.gutenberg.org/files/1549/1549-h/1549-h.htm#link2HCH0002','2020-05-18 23:31:10'), 66 | ('Obsession With Fraud Sabotages U.S. Aid to Millions Without Jobs','www.bloomberg.com',"The safeguards often end up doing more harm than good, a researcher says.",'https://assets.bwbx.io/images/users/iqjWHBFdfxIU/izhcpElAS5qA/v1/1200x691.jpg',1135,'https://www.bloomberg.com/news/articles/2020-05-07/obsession-with-fraud-sabotages-u-s-aid-to-millions-without-jobs?srnd=premium&sref=73c0pvQV','2020-05-18 23:32:05'), 67 | ('April Snapshot','www.katerra.com','','',623,'https://www.katerra.com/2020/04/30/april-snapshot/','2020-05-18 23:32:51'), 68 | ('Danaos expands fleet with secondhand acquisitions','splash247.com','','',188,'https://splash247.com/danaos-expands-fleet-with-secondhand-acquisitions/','2020-05-18 23:33:32'), 69 | ('Matt Cogar','www.redbull.com','','',17,'https://www.redbull.com/us-en/athlete/matt-cogar','2020-05-18 23:36:39'), 70 | ('Maersk launches its cold store in St. Petersburg (Russia)','www.maersk.com','','',335,'https://www.maersk.com/news/articles/2020/04/17/maersk-launches-its-cold-store-in-st-petersburg-russia','2020-05-18 23:37:23'), 71 | ('How to ensure artificial intelligence benefits society: A conversation with Stuart Russell and James Manyika','www.mckinsey.com','','',2448,'https://www.mckinsey.com/featured-insights/artificial-intelligence/how-to-ensure-artificial-intelligence-benefits-society-a-conversation-with-stuart-russell-and-james-manyika','2020-05-18 23:37:53'), 72 | ("Who’s Behind the Reopen Domain Surge? — Krebs on Security",'krebsonsecurity.com','','',11,'https://krebsonsecurity.com/2020/04/whos-behind-the-reopen-domain-surge/','2020-05-18 23:38:21'), 73 | ('The sprint to solve coronavirus protein structures — and disarm them with drugs','www.nature.com','Stopping the pandemic could rely on breakneck efforts to visualize SARS-CoV-2 proteins and use them to design drugs and vaccines.','https://media.nature.com/lw1024/magazine-assets/d41586-020-01444-z/d41586-020-01444-z_17985490.jpg',2914,'https://www.nature.com/articles/d41586-020-01444-z','2020-05-18 23:39:17'), 74 | ('Knowledge Debt','amir.rachum.com','','',682,'https://amir.rachum.com/amp/blog/2016/09/15/knowledge-debt.html','2020-05-18 23:39:37'), 75 | ('Taste for Makers','www.paulgraham.com','','',4257,'http://www.paulgraham.com/taste.html','2020-05-18 23:40:03'), 76 | ("College basketball's 'greatest of all time' bracket -- South Region breakdown",'www.espn.com','','',901,'https://www.espn.com/mens-college-basketball/story/_/page/playerbracket-south/college-basketball-greatest-all-bracket-south-region-breakdown','2020-05-19 0:07:23'); 77 | 78 | -- TAGS 79 | INSERT INTO tags ( 80 | tag_name, 81 | created_at, 82 | count 83 | ) VALUES 84 | ('basketball','2020-05-18 23:39:17',2), 85 | ('tech','2020-05-18 23:39:37',3), 86 | ('covid','2020-05-18 23:40:03',3), 87 | ('freight','2020-05-19 0:07:23',1); 88 | 89 | -- SUBSCRIPTIONS 90 | INSERT INTO subscriptions ( 91 | subscriber_id, 92 | publisher_id, 93 | created_at, 94 | isNew 95 | ) VALUES 96 | (1,2,'2020-04-30 16:42:16',0), 97 | (1,3,'2020-04-30 16:42:11',0), 98 | (1,4,'2020-08-08 12:03:19',1), 99 | (2,1,'2020-03-27 11:09:59',0), 100 | (2,3,'2020-04-12 23:55:26',1), 101 | (2,4,'2020-04-23 0:54:05',0), 102 | (2,5,'2020-05-27 22:34:14',0), 103 | (3,2,'2020-08-09 0:16:48',0), 104 | (3,4,'2020-08-09 0:16:54',0), 105 | (4,1,'2020-03-27 12:10:16',1), 106 | (4,2,'2020-03-27 12:10:13',0), 107 | (4,3,'2020-03-27 12:10:14',0), 108 | (5,2,'2020-04-30 11:13:07',1); 109 | 110 | -- FAVORITES 111 | INSERT INTO favorites ( 112 | user_id, 113 | reading_id, 114 | created_at 115 | ) VALUES 116 | (1,2,'2020-07-24 20:14:25'), 117 | (2,1,'2020-05-26 14:19:00'), 118 | (2,4,'2020-05-26 14:18:52'), 119 | (2,19,'2020-05-26 14:18:48'), 120 | (2,35,'2020-05-26 14:18:46'), 121 | (4,15,'2020-05-28 18:39:10'), 122 | (4,16,'2020-05-28 20:24:38'), 123 | (5,13,'2020-08-08 23:04:39'), 124 | (5,45,'2020-08-08 23:04:41'); 125 | 126 | -- READING TAGS 127 | INSERT INTO reading_tags ( 128 | reading_id, 129 | tag_id 130 | ) VALUES 131 | (35,1), 132 | (36,2), 133 | (37,3), 134 | (40,3), 135 | (44,4), 136 | (45,2), 137 | (46,2), 138 | (47,3), 139 | (50,1); 140 | 141 | -- USER READINGS 142 | INSERT INTO user_readings ( 143 | user_id, 144 | reading_id 145 | ) VALUES 146 | (1,2), 147 | (1,5), 148 | (1,6), 149 | (1,29), 150 | (1,30), 151 | (1,31), 152 | (1,47), 153 | (1,48), 154 | (2,1), 155 | (2,4), 156 | (2,9), 157 | (2,17), 158 | (2,18), 159 | (2,19), 160 | (2,33), 161 | (2,35), 162 | (2,36), 163 | (2,37), 164 | (2,38), 165 | (2,41), 166 | (2,46), 167 | (3,3), 168 | (3,7), 169 | (3,8), 170 | (3,14), 171 | (3,34), 172 | (3,39), 173 | (3,40), 174 | (4,10), 175 | (4,15), 176 | (4,16), 177 | (4,24), 178 | (4,25), 179 | (4,26), 180 | (4,27), 181 | (4,28), 182 | (4,42), 183 | (4,43), 184 | (4,50), 185 | (5,11), 186 | (5,12), 187 | (5,13), 188 | (5,20), 189 | (5,21), 190 | (5,22), 191 | (5,23), 192 | (5,32), 193 | (5,44), 194 | (5,45), 195 | (5,49); 196 | 197 | -- USER TAGS 198 | INSERT INTO user_tags ( 199 | user_id, 200 | tag_id 201 | ) VALUES 202 | (1,3), 203 | (2,1), 204 | (2,2), 205 | (2,3), 206 | (2,2), 207 | (3,3), 208 | (4,1), 209 | (5,4), 210 | (5,2); -------------------------------------------------------------------------------- /src/mysql/tables.sql: -------------------------------------------------------------------------------- 1 | SET FOREIGN_KEY_CHECKS = 0; 2 | 3 | DROP TABLE IF EXISTS reading_tags; 4 | DROP TABLE IF EXISTS favorites; 5 | DROP TABLE IF EXISTS subscriptions; 6 | DROP TABLE IF EXISTS user_readings; 7 | DROP TABLE IF EXISTS user_tags; 8 | DROP TABLE IF EXISTS tags; 9 | DROP TABLE IF EXISTS readings; 10 | DROP TABLE IF EXISTS users; 11 | 12 | -- USERS 13 | CREATE TABLE users ( 14 | id int(11) NOT NULL AUTO_INCREMENT, 15 | first_name varchar(255) NOT NULL, 16 | last_name varchar(255) NOT NULL, 17 | email varchar(255) NOT NULL, 18 | username varchar(255) NOT NULL, 19 | password varchar(255) DEFAULT NULL, 20 | image varchar(255) DEFAULT NULL, 21 | created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, 22 | PRIMARY KEY (id), 23 | UNIQUE KEY email (email), 24 | UNIQUE KEY username (username), 25 | FULLTEXT KEY first_name (first_name,last_name,username) 26 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 27 | 28 | -- READINGS 29 | CREATE TABLE readings ( 30 | id int(11) NOT NULL AUTO_INCREMENT, 31 | title varchar(255) NOT NULL, 32 | domain varchar(255) NOT NULL, 33 | description varchar(500) DEFAULT NULL, 34 | image varchar(500) DEFAULT NULL, 35 | word_count int(11) NOT NULL, 36 | url varchar(255) NOT NULL, 37 | created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, 38 | PRIMARY KEY (id) 39 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 40 | 41 | -- TAGS 42 | CREATE TABLE tags ( 43 | id int(11) NOT NULL AUTO_INCREMENT, 44 | tag_name varchar(255) DEFAULT NULL, 45 | created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, 46 | count int(11) DEFAULT 1, 47 | PRIMARY KEY (id), 48 | UNIQUE KEY tag_name (tag_name) 49 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 50 | 51 | -- SUBSCRIPTIONS 52 | CREATE TABLE subscriptions ( 53 | subscriber_id int(11) NOT NULL, 54 | publisher_id int(11) NOT NULL, 55 | created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, 56 | isNew int(11) DEFAULT 1, 57 | PRIMARY KEY (subscriber_id,publisher_id), 58 | KEY publisher_id (publisher_id), 59 | CONSTRAINT subscriptions_ibfk_1 FOREIGN KEY (subscriber_id) REFERENCES users (id), 60 | CONSTRAINT subscriptions_ibfk_2 FOREIGN KEY (publisher_id) REFERENCES users (id) 61 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 62 | 63 | -- FAVORITES 64 | CREATE TABLE favorites ( 65 | user_id int(11) NOT NULL, 66 | reading_id int(11) NOT NULL, 67 | created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, 68 | PRIMARY KEY (user_id,reading_id), 69 | KEY reading_id (reading_id), 70 | CONSTRAINT favorites_ibfk_1 FOREIGN KEY (user_id) REFERENCES users (id), 71 | CONSTRAINT favorites_ibfk_2 FOREIGN KEY (reading_id) REFERENCES readings (id) 72 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 73 | 74 | -- READING TAGS 75 | CREATE TABLE reading_tags ( 76 | reading_id int(11) NOT NULL, 77 | tag_id int(11) NOT NULL, 78 | PRIMARY KEY (reading_id,tag_id), 79 | KEY tag_id (tag_id), 80 | CONSTRAINT reading_tags_ibfk_1 FOREIGN KEY (reading_id) REFERENCES readings (id), 81 | CONSTRAINT reading_tags_ibfk_2 FOREIGN KEY (tag_id) REFERENCES tags (id) 82 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 83 | 84 | -- USER READINGS 85 | CREATE TABLE user_readings ( 86 | user_id int(11) NOT NULL, 87 | reading_id int(11) NOT NULL, 88 | KEY user_id (user_id), 89 | KEY reading_id (reading_id), 90 | CONSTRAINT user_readings_ibfk_1 FOREIGN KEY (user_id) REFERENCES users (id), 91 | CONSTRAINT user_readings_ibfk_2 FOREIGN KEY (reading_id) REFERENCES readings (id) 92 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 93 | 94 | -- USER TAGS 95 | CREATE TABLE user_tags ( 96 | user_id int(11) NOT NULL, 97 | tag_id int(11) NOT NULL, 98 | KEY user_id (user_id), 99 | KEY tag_id (tag_id), 100 | CONSTRAINT user_tags_ibfk_1 FOREIGN KEY (user_id) REFERENCES users (id), 101 | CONSTRAINT user_tags_ibfk_2 FOREIGN KEY (tag_id) REFERENCES tags (id) 102 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 103 | -------------------------------------------------------------------------------- /src/queries/reading.js: -------------------------------------------------------------------------------- 1 | exports.insertReading = `INSERT INTO readings (title, domain, description, image, word_count, url) VALUES ?`; 2 | exports.insertUserReading = `INSERT INTO user_readings (user_id, reading_id) VALUES (?, ?)`; 3 | exports.selectReadingById = `SELECT * FROM readings WHERE id = ?`; 4 | exports.updateFavorite = `INSERT INTO favorites (user_id, reading_id) VALUES (?, ?)`; 5 | exports.deleteFavorite = `DELETE FROM favorites WHERE user_id = ? AND reading_id = ?`; 6 | exports.deleteReading = `DELETE ur, f, rt FROM user_readings ur 7 | LEFT JOIN favorites f ON f.reading_id = ur.reading_id AND f.user_id = ur.user_id 8 | LEFT JOIN reading_tags rt ON rt.reading_id = ur.reading_ID 9 | WHERE ur.user_id = ? AND ur.reading_id = ?`; 10 | exports.updateReading = `UPDATE readings SET ? WHERE id = ?`; 11 | exports.selectReadingIdByUrl = `SELECT id FROM readings WHERE url = ?`; 12 | 13 | exports.selectAllReadings = ` 14 | SELECT 15 | user_readings.reading_id, 16 | readings.title, 17 | readings.domain, 18 | readings.description, 19 | readings.image as readings_image, 20 | readings.word_count, 21 | readings.url, 22 | readings.created_at, 23 | user_readings.user_id, 24 | users.username, 25 | users.image, 26 | favorites.user_id as favorite, 27 | GROUP_CONCAT(reading_tags.tag_id) as tag_ids 28 | FROM user_readings 29 | LEFT JOIN readings ON readings.id = user_readings.reading_id 30 | LEFT JOIN users ON users.id = user_readings.user_id 31 | LEFT JOIN favorites ON favorites.reading_id = user_readings.reading_id 32 | LEFT JOIN reading_tags ON reading_tags.reading_id = user_readings.reading_id 33 | GROUP BY 34 | user_readings.reading_id, 35 | readings.title, 36 | readings.domain, 37 | readings.description, 38 | readings.image, 39 | readings.word_count, 40 | readings.url, 41 | readings.created_at, 42 | user_readings.user_id, 43 | users.username, 44 | users.image, 45 | favorites.user_id 46 | ORDER BY user_readings.reading_id`; 47 | 48 | exports.selectUserReadings = ` 49 | SELECT 50 | user_readings.reading_id, 51 | readings.title, 52 | readings.domain, 53 | readings.description, 54 | readings.image as readings_image, 55 | readings.word_count, 56 | readings.url, 57 | readings.created_at, 58 | user_readings.user_id, 59 | users.username, 60 | users.image, 61 | favorites.user_id as favorite, 62 | GROUP_CONCAT(reading_tags.tag_id) as tag_ids 63 | FROM user_readings 64 | LEFT JOIN readings ON readings.id = user_readings.reading_id 65 | LEFT JOIN users ON users.id = user_readings.user_id 66 | LEFT JOIN favorites ON favorites.reading_id = user_readings.reading_id 67 | LEFT JOIN reading_tags ON reading_tags.reading_id = user_readings.reading_id 68 | WHERE user_readings.user_id = ? 69 | GROUP BY 70 | user_readings.reading_id, 71 | readings.title, 72 | readings.domain, 73 | readings.description, 74 | readings.image, 75 | readings.word_count, 76 | readings.url, 77 | readings.created_at, 78 | user_readings.user_id, 79 | users.username, 80 | users.image, 81 | favorites.user_id 82 | ORDER BY user_readings.reading_id DESC`; 83 | 84 | exports.selectSubscriptionReadings = ` 85 | SELECT 86 | user_readings.reading_id, 87 | readings.title, 88 | readings.domain, 89 | readings.description, 90 | readings.image as readings_image, 91 | readings.word_count, 92 | readings.url, 93 | readings.created_at, 94 | user_readings.user_id, 95 | users.username, 96 | users.image, 97 | favorites.user_id as favorite, 98 | GROUP_CONCAT(reading_tags.tag_id) as tag_ids 99 | FROM subscriptions 100 | INNER JOIN user_readings ON user_readings.user_id = publisher_id 101 | LEFT JOIN readings ON readings.id = user_readings.reading_id 102 | LEFT JOIN users ON users.id = user_readings.user_id 103 | LEFT JOIN favorites ON favorites.reading_id = user_readings.reading_id 104 | LEFT JOIN reading_tags ON reading_tags.reading_id = user_readings.reading_id 105 | WHERE subscriber_id = ? 106 | GROUP BY 107 | user_readings.reading_id, 108 | readings.title, 109 | readings.domain, 110 | readings.description, 111 | readings.image, 112 | readings.word_count, 113 | readings.url, 114 | readings.created_at, 115 | user_readings.user_id, 116 | users.username, 117 | users.image, 118 | favorites.user_id 119 | ORDER BY user_readings.reading_id DESC`; 120 | 121 | exports.selectFavoriteReadings = ` 122 | SELECT 123 | favorites.reading_id, 124 | readings.title, 125 | readings.domain, 126 | readings.description, 127 | readings.image as readings_image, 128 | readings.word_count, 129 | readings.url, 130 | readings.created_at, 131 | favorites.user_id, 132 | users.username, 133 | users.image, 134 | favorites.user_id as favorite, 135 | GROUP_CONCAT(reading_tags.tag_id) as tag_ids 136 | FROM favorites 137 | LEFT JOIN readings ON readings.id = favorites.reading_id 138 | LEFT JOIN users ON users.id = favorites.user_id 139 | LEFT JOIN reading_tags ON reading_tags.reading_id = favorites.reading_id 140 | WHERE favorites.user_id = ? 141 | GROUP BY 142 | favorites.reading_id, 143 | readings.title, 144 | readings.domain, 145 | readings.description, 146 | readings.image, 147 | readings.word_count, 148 | readings.url, 149 | readings.created_at, 150 | favorites.user_id, 151 | users.username, 152 | users.image, 153 | favorites.user_id 154 | ORDER BY readings.id DESC`; 155 | -------------------------------------------------------------------------------- /src/queries/subscription.js: -------------------------------------------------------------------------------- 1 | exports.insertSubscription = `INSERT INTO subscriptions SET ?`; 2 | exports.selectSubscriptionById = `SELECT * FROM subscriptions WHERE subscriber_id = ?`; 3 | exports.deleteSubscription = `DELETE FROM subscriptions WHERE subscriber_id = ? AND publisher_id = ?`; 4 | -------------------------------------------------------------------------------- /src/queries/tag.js: -------------------------------------------------------------------------------- 1 | exports.insertTags = `INSERT INTO tags (tag_name) VALUES ? ON DUPLICATE KEY UPDATE count = count + 1`; 2 | exports.insertReadingTags = `INSERT INTO reading_tags (reading_id, tag_id) VALUES ?`; 3 | exports.insertUserTags = `INSERT INTO user_tags (user_id, tag_id) VALUES ?`; 4 | exports.selectIdByTagName = `SELECT id FROM tags WHERE tag_name = ?`; 5 | exports.deleteTags = `DELETE FROM reading_tags WHERE reading_id = ?`; 6 | exports.deleteUserTags = `DELETE FROM user_tags WHERE (user_id, tag_id) IN ?`; 7 | exports.deleteReadingTags = `DELETE FROM reading_tags WHERE (reading_id, tag_id) IN ?`; 8 | 9 | exports.selectAllTags = ` 10 | SELECT 11 | tags.id, 12 | tag_name, 13 | GROUP_CONCAT(reading_tags.reading_id) as reading_id, 14 | created_at as date, 15 | count 16 | FROM reading_tags 17 | LEFT JOIN tags ON tags.id = reading_tags.tag_id 18 | GROUP BY 19 | tags.id, 20 | tag_name, 21 | created_at, 22 | count 23 | ORDER BY created_at DESC`; 24 | 25 | exports.selectUserTags = ` 26 | SELECT 27 | tags.id, 28 | tag_name, 29 | GROUP_CONCAT(user_tags.user_id) as user_id, 30 | created_at as date, 31 | count 32 | FROM user_tags 33 | LEFT JOIN tags ON tags.id = user_tags.tag_id 34 | WHERE user_tags.user_id = ? 35 | GROUP BY 36 | tags.id, 37 | tag_name, 38 | created_at, 39 | count 40 | ORDER BY created_at DESC`; 41 | 42 | exports.selectSubscriptionTags = ` 43 | SELECT 44 | tags.id, 45 | tag_name, 46 | GROUP_CONCAT(user_tags.user_id) as user_id, 47 | tags.created_at as date, 48 | count 49 | FROM subscriptions 50 | INNER JOIN user_tags ON user_tags.user_id = publisher_id 51 | LEFT JOIN tags ON tags.id = user_tags.tag_id 52 | WHERE subscriber_id = ? 53 | GROUP BY 54 | tags.id, 55 | tag_name, 56 | tags.created_at, 57 | count 58 | ORDER BY tags.created_at DESC`; 59 | -------------------------------------------------------------------------------- /src/queries/user.js: -------------------------------------------------------------------------------- 1 | exports.insertUser = `INSERT INTO users (first_name, last_name, username, email, password, image) VALUES ?`; 2 | exports.selectByUsername = `SELECT * FROM users WHERE username = ?`; 3 | exports.selectById = `SELECT id, username, image FROM users WHERE id = ?`; 4 | exports.selectByEmail = `SELECT * FROM users WHERE email = ?`; 5 | exports.deleteUser = `DELETE FROM users WHERE username = ?`; 6 | exports.selectAllUsers = `SELECT id, first_name, last_name, username, image FROM users ORDER BY id DESC`; 7 | exports.updateUser = `UPDATE users SET image = ?, username = ? WHERE id = ?`; 8 | exports.updateUserPassword = `UPDATE users SET password = ? WHERE id = ?`; 9 | exports.selectUserFavorites = `SELECT reading_id, user_id FROM favorites WHERE user_id = ?`; 10 | 11 | exports.selectUserSubscriptions = ` 12 | SELECT 13 | id, 14 | username, 15 | image 16 | FROM subscriptions 17 | LEFT JOIN users ON publisher_id = users.id 18 | WHERE subscriber_id = ? 19 | ORDER BY id DESC`; 20 | 21 | exports.selectUserSubscribers = ` 22 | SELECT 23 | id, 24 | username, 25 | image 26 | FROM subscriptions 27 | LEFT JOIN users ON subscriber_id = users.id 28 | WHERE publisher_id = ? 29 | ORDER BY id DESC`; 30 | 31 | exports.searchUsers = ` 32 | SELECT 33 | id, 34 | first_name, 35 | last_name, 36 | username, 37 | image, 38 | MATCH 39 | (first_name, last_name, username) 40 | AGAINST (?) as score 41 | FROM users 42 | WHERE MATCH 43 | (first_name, last_name, username) 44 | AGAINST (?) > 0 45 | ORDER BY score DESC`; 46 | -------------------------------------------------------------------------------- /src/reading_scraper.py: -------------------------------------------------------------------------------- 1 | import requests, re, sys, json, os 2 | from urllib.parse import unquote 3 | from random import choice 4 | from dotenv import load_dotenv 5 | from fake_useragent import UserAgent, FakeUserAgentError 6 | from goose3 import Goose 7 | 8 | load_dotenv() 9 | link_preview = os.getenv('LINK_PREVIEW_KEY') 10 | 11 | # Scraper class defined here. Ctrl + F for "Scraper Logic" 12 | # to skip the class definition 13 | class Scraper(): 14 | # Since the only thing an user submits is a url, 15 | # we init class with url, and then gather data from there 16 | def __init__(self, url): 17 | self.url = url 18 | self.is_pdf = False 19 | self.reading = '' 20 | self.title = '' 21 | self.description = '' 22 | self.word_count = 0 23 | self.image = '' 24 | self.domain = '' 25 | self.headers = '' 26 | self.useragent = '' 27 | self.errors = [] 28 | 29 | # make sure the url is valid 30 | @classmethod 31 | def transform_url(cls, url_str): 32 | # check first if it's valid, and if it is then return the class 33 | try: 34 | r = requests.get(url_str, timeout=10) 35 | except TimeoutError: 36 | # timeout error probably means the url is good, but there's some server issue 37 | return cls(url_str) 38 | except: 39 | r = "" 40 | pass 41 | if r != "" and r.status_code != 404: 42 | return cls(url_str) 43 | 44 | # if it's not valid, try to make it valid 45 | url = unquote(url_str) 46 | url = url.replace(" ", "%20") 47 | 48 | has_www = re.match(r"www\.", url) 49 | has_http = re.match(r"http", url) 50 | 51 | if has_http and not has_www: 52 | # print(f"transformed and unquoted url: {url}") 53 | return cls(url) 54 | 55 | if not has_www: 56 | url = "www." + url 57 | if not has_http: 58 | url = "https://" + url 59 | # retry url now that it's been "fixed" 60 | try: 61 | r = requests.get(url, timeout=10) 62 | except TimeoutError: 63 | # timeout error probably means the url is good, but there's some server issue 64 | return cls(url) 65 | except: 66 | r = "" 67 | pass 68 | if r != "" and r.status_code != 404: 69 | return cls(url) 70 | # if it's still not valid, then tell the user. 71 | else: 72 | values = ["Invalid URL.", "", "", "", 0, url_str] 73 | return values 74 | 75 | def useragent_generator(self): 76 | ua_list = [ # needs to have more options 77 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Safari/605.1.15', 78 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0', 79 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36', 80 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:77.0) Gecko/20100101 Firefox/77.0', 81 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36', 82 | ] 83 | fallback = choice(ua_list) 84 | try: 85 | ua = UserAgent(fallback=fallback, cache=False, verify_ssl=False) 86 | except FakeUserAgentError: 87 | self.errors.append(sys.exc_info()) 88 | # if there is an error with the package, set the user agent explicitly. 89 | # may end up just using this as the function. 90 | self.useragent = fallback 91 | return self.useragent 92 | self.useragent = str(ua.random) 93 | # print(headers) 94 | return self.useragent 95 | 96 | def get_domain(self): 97 | try: 98 | domain = re.search(r'(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]', self.url) 99 | domain = domain.group() 100 | except: 101 | self.domain = 'Unable to get domain' 102 | # print("Error occurred with getting domain: ", sys.exc_info()) 103 | self.errors.append(sys.exc_info()) 104 | # print(domain) 105 | self.domain = domain 106 | return self.domain 107 | 108 | def check_if_pdf(self): 109 | is_pdf = re.findall(r".pdf", self.url, flags=re.IGNORECASE) 110 | if len(is_pdf) > 0: 111 | self.is_pdf = True 112 | else: 113 | self.is_pdf = False 114 | 115 | return self.is_pdf 116 | 117 | def build_headers(self): 118 | headers = { 119 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', 120 | 'Accept-Encoding': 'gzip, deflate, br', 121 | 'Accept-Language': 'en-US,en;q=0.9', 122 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 123 | 'Cache-Control': 'no-cache', 124 | 'DNT': '1', 125 | 'Pragma': 'no-cache', 126 | 'Referer': "https://www.google.com/search", 127 | 'User-Agent': self.useragent, 128 | 'Sec-Fetch-Dest': 'document', 129 | 'Sec-Fetch-Mode': 'navigate', 130 | 'Sec-Fetch-Site': 'cross-site', 131 | 'Upgrade-Insecure-Requests': '1', 132 | } 133 | 134 | self.headers = headers 135 | 136 | # print(headers) 137 | 138 | return headers 139 | 140 | def get_reading_data(self): 141 | g = Goose({'http_headers': self.headers}) 142 | # g = Goose({'browser_user_agent': useragent_generator()}) 143 | # extract info with goose 144 | try: 145 | self.reading = g.extract(url = self.url) 146 | except: 147 | # print("Error occurred with 1st try getting reading data: ", sys.exc_info()) 148 | self.reading = '' 149 | 150 | # if domain is 'special' or if title is blank, try cached version 151 | if (self.reading == '' or self.reading.title == '' 152 | or '403 Forbidden' in self.reading.title 153 | or "Bloomberg - Are" in self.reading.title): 154 | # print('needing to cache') 155 | cached_url = 'https://webcache.googleusercontent.com/search?q=cache:' + self.url 156 | try: 157 | self.reading = g.extract(url=cached_url) 158 | except: 159 | # print("Error occurred with 1st try getting reading data: ", sys.exc_info()) 160 | self.reading = '' 161 | # print(self.reading.title) 162 | 163 | # if we cache and 404 error is thrown, back to normal 164 | if ('Not Found' in self.reading.title 165 | or str(self.reading.title).startswith("https:///search?q=cache:") 166 | or '404' in self.reading.title 167 | or "Bloomberg - Are" in self.reading.title 168 | or self.reading == ''): 169 | # print('using link preview') 170 | r = requests.get(f'http://api.linkpreview.net/?key={link_preview}&q={self.url}') 171 | j = r.json() 172 | # print(j) 173 | self.title = j['title'] 174 | self.description = j['description'] 175 | self.image = j['image'] 176 | 177 | 178 | # print(reading) 179 | 180 | def get_title(self): 181 | try: 182 | if self.reading != 'None' and self.reading.title != '' and self.title == '': 183 | if ('title' in self.reading.opengraph): 184 | self.title = self.reading.opengraph['title'].replace("\\u2019", "'") 185 | if (self.title == '' and self.reading.title): 186 | self.title = self.reading.title.replace("\\u2019", "'") 187 | 188 | if ('description' in self.reading.opengraph): 189 | self.description = self.reading.opengraph['description'].replace("\\u2019", "'") 190 | if (self.description == '' and self.reading.meta_description): 191 | self.description = self.reading.meta_description.replace("\\u2019", "'") 192 | # if we still can't get a description, then just use the first 500 characters of text. 193 | if (self.description == '' and self.reading.cleaned_text): 194 | self.description = self.reading.cleaned_text.replace("\\u2019", "'") 195 | if (len(self.description) > 500): 196 | self.description = (self.description[:497] + '...') 197 | 198 | if ('image' in self.reading.opengraph): 199 | self.image = self.reading.opengraph['image'] 200 | if (self.image == '' and self.reading.top_image): 201 | self.image = self.reading.top_image 202 | except: 203 | # print("Error occurred with getting title: ", sys.exc_info()) 204 | self.title = "" 205 | 206 | if (self.title == '' 207 | or 'Error 404 (Not Found)' in self.title 208 | or 'Bad Request' in self.title 209 | or self.title == "Redirecting" 210 | or "Page Not Found" in self.title): 211 | self.title = 'Unable to get title of article' 212 | self.description = '' 213 | self.image = '' 214 | # print(title) 215 | 216 | def get_word_count(self): 217 | try: 218 | if self.reading != 'None': 219 | word_list = self.reading.cleaned_text.split(' ') 220 | else: 221 | word_list = ['None'] 222 | self.word_count = len(word_list) 223 | except: 224 | self.word_count = 0 225 | self.errors.append(sys.exc_info()) 226 | # print(word_count) 227 | 228 | # Scraper Logic 229 | BASE_URL = sys.argv[1] 230 | base_scrape = Scraper.transform_url(BASE_URL) 231 | if isinstance(base_scrape, list): 232 | values = ["Invalid URL.", "", "", "", 0, BASE_URL] 233 | print(json.dumps(values)) 234 | sys.exit() 235 | 236 | base_scrape.useragent_generator() 237 | base_scrape.build_headers() 238 | base_scrape.get_domain() 239 | base_scrape.get_reading_data() 240 | base_scrape.get_title() 241 | base_scrape.get_word_count() 242 | 243 | values = [ 244 | base_scrape.title, 245 | base_scrape.domain, 246 | base_scrape.description, 247 | base_scrape.image, 248 | base_scrape.word_count, 249 | BASE_URL.replace("\n", "") 250 | ] 251 | 252 | # print(values) 253 | # print to send data to node.js 254 | print(json.dumps(values)) -------------------------------------------------------------------------------- /src/reading_summary.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from bs4 import BeautifulSoup 3 | import sys 4 | from dotenv import load_dotenv 5 | from gensim.summarization import summarize 6 | from fake_useragent import UserAgent 7 | from fake_useragent import FakeUserAgentError 8 | from goose3 import Goose 9 | 10 | load_dotenv() 11 | 12 | BASE_URL = sys.argv[1] 13 | body = '' 14 | summary = '' 15 | 16 | def useragent_generator(): 17 | fallback = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36' 18 | try: 19 | ua = UserAgent(fallback=fallback) 20 | except FakeUserAgentError: 21 | pass 22 | headers = str(ua.random) 23 | return headers 24 | 25 | def get_reading(): 26 | global body 27 | try: 28 | g = Goose({'browser_user_agent': useragent_generator()}) 29 | reading = g.extract(url=BASE_URL) 30 | body = reading.cleaned_text 31 | except: 32 | body = 'None' 33 | 34 | def get_summary(): 35 | global summary 36 | summary = summarize(body, word_count=500) 37 | print(summary) 38 | 39 | get_reading() 40 | get_summary() -------------------------------------------------------------------------------- /src/routes/auth.js: -------------------------------------------------------------------------------- 1 | let express = require("express"), 2 | router = express.Router(), 3 | auth = require("../controllers/auth"), 4 | { handleFormData } = require("../middleware/auth"); 5 | // { cloudinaryConfig } = require('../middleware/auth'); 6 | 7 | // router.post('/signup', upload.single('image'), cloudinaryConfig, auth.signup); 8 | router.post("/signup", handleFormData.single("image"), auth.signup); 9 | router.post("/signin", auth.signin); 10 | router.post("/reset", auth.sendPasswordResetEmail); 11 | router.post("/:username/reset/:token", auth.updatePassword); 12 | module.exports = router; 13 | -------------------------------------------------------------------------------- /src/routes/cspReport.js: -------------------------------------------------------------------------------- 1 | let express = require("express"), 2 | router = express.Router({ mergeParams: true }); 3 | 4 | router.post("", (req, res) => { 5 | console.log("CSP header violation", req.body["csp-report"]); 6 | return res.status(204).json({ report: "CSP header violation" }); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /src/routes/readings.js: -------------------------------------------------------------------------------- 1 | let express = require("express"), 2 | router = express.Router({ mergeParams: true }), 3 | readings = require("../controllers/readings"); 4 | 5 | router.get("", readings.findAllReadings); 6 | router.get("/:id", readings.findUserReadings); 7 | router.put("/:reading_id", readings.updateReading); 8 | router.get("/:id/subscriptions", readings.findSubscriptionReadings); 9 | router.get("/:id/favorites", readings.findFavoriteReadings); 10 | router.get("/:id/summary", readings.summarizeReading); 11 | router.post("/:id/favorite/:user_id", readings.markFavorite); 12 | router.delete("/:id/favorite/:user_id", readings.deleteFavorite); 13 | 14 | module.exports = router; 15 | -------------------------------------------------------------------------------- /src/routes/search.js: -------------------------------------------------------------------------------- 1 | let express = require("express"), 2 | router = express.Router({ mergeParams: true }), 3 | search = require("../controllers/search"); 4 | 5 | router.get("/", search.searchAll); 6 | router.get("/users", search.searchUsers); 7 | 8 | module.exports = router; 9 | -------------------------------------------------------------------------------- /src/routes/tags.js: -------------------------------------------------------------------------------- 1 | let express = require("express"), 2 | router = express.Router({ mergeParams: true }), 3 | tags = require("../controllers/tags"); 4 | 5 | router.get("", tags.findAllTags); 6 | router.get("/:id", tags.findUserTags); 7 | router.post("/:id", tags.createTags); 8 | router.put("/:id", tags.updateTags); 9 | router.get("/:id/subscriptions", tags.findSubscriptionTags); 10 | 11 | module.exports = router; 12 | -------------------------------------------------------------------------------- /src/routes/users.js: -------------------------------------------------------------------------------- 1 | let express = require("express"), 2 | router = express.Router({ mergeParams: true }), 3 | auth = require("../middleware/auth"), 4 | users = require("../controllers/users"), 5 | notifications = require("../controllers/notifications"), 6 | subscriptions = require("../controllers/subscriptions"), 7 | readings = require("../controllers/readings"); 8 | 9 | router.get("", users.findAllUsers); 10 | router.get("/:id", users.findUser); 11 | router.put("/:id", users.updateUser); 12 | // DELETE /api/users/:id 13 | 14 | // READINGS 15 | router.post( 16 | "/:id/readings", 17 | auth.loginRequired, 18 | auth.ensureCorrectUser, 19 | readings.createReading 20 | ); 21 | router.delete( 22 | "/:id/readings/:reading_id", 23 | auth.loginRequired, 24 | auth.ensureCorrectUser, 25 | readings.deleteReading 26 | ); 27 | 28 | // SUBSCRIPTIONS 29 | router.get("/:id/subscriptions", users.findSubscriptions); 30 | router.post("/:id/subscriptions", subscriptions.createSubscription); 31 | router.delete( 32 | "/:user_id/subscriptions/:sub_id", 33 | subscriptions.deleteSubscription 34 | ); 35 | 36 | // NOTIFICATIONS 37 | router.get("/:id/notifications", notifications.findNewSubscriptions); 38 | router.put("/:id/notifications", notifications.removeNotification); 39 | 40 | // FAVORITES 41 | router.get("/:id/favorites", users.findFavorites); 42 | 43 | module.exports = router; 44 | --------------------------------------------------------------------------------