Compare commits

..

1 Commits

Author SHA1 Message Date
Andrew Bastin
7ca39d6365 chore: add release tag ci pipeline to push to docker hub 2023-08-31 14:59:10 +05:30
25 changed files with 358 additions and 899 deletions

View File

@@ -29,8 +29,8 @@ jobs:
target: backend target: backend
push: true push: true
tags: | tags: |
${{ secrets.DOCKER_ORG_NAME }}/${{ secrets.DOCKER_BACKEND_CONTAINER_NAME }}:latest ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_BACKEND_CONTAINER_NAME }}:latest
${{ secrets.DOCKER_ORG_NAME }}/${{ secrets.DOCKER_BACKEND_CONTAINER_NAME }}:${{ github.ref_name }} ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_BACKEND_CONTAINER_NAME }}:${{ github.ref }}
- name: Build and push `${{ secrets.DOCKER_FRONTEND_CONTAINER_NAME }}` - name: Build and push `${{ secrets.DOCKER_FRONTEND_CONTAINER_NAME }}`
uses: docker/build-push-action@v4 uses: docker/build-push-action@v4
@@ -40,8 +40,8 @@ jobs:
target: app target: app
push: true push: true
tags: | tags: |
${{ secrets.DOCKER_ORG_NAME }}/${{ secrets.DOCKER_FRONTEND_CONTAINER_NAME }}:latest ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_FRONTEND_CONTAINER_NAME }}:latest
${{ secrets.DOCKER_ORG_NAME }}/${{ secrets.DOCKER_FRONTEND_CONTAINER_NAME }}:${{ github.ref_name }} ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_FRONTEND_CONTAINER_NAME }}:${{ github.ref }}
- name: Build and push `${{ secrets.DOCKER_SH_ADMIN_CONTAINER_NAME }}` - name: Build and push `${{ secrets.DOCKER_SH_ADMIN_CONTAINER_NAME }}`
uses: docker/build-push-action@v4 uses: docker/build-push-action@v4
@@ -51,8 +51,8 @@ jobs:
target: sh_admin target: sh_admin
push: true push: true
tags: | tags: |
${{ secrets.DOCKER_ORG_NAME }}/${{ secrets.DOCKER_SH_ADMIN_CONTAINER_NAME }}:latest ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_SH_ADMIN_CONTAINER_NAME }}:latest
${{ secrets.DOCKER_ORG_NAME }}/${{ secrets.DOCKER_SH_ADMIN_CONTAINER_NAME }}:${{ github.ref_name }} ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_SH_ADMIN_CONTAINER_NAME }}:${{ github.ref }}
- name: Build and push `${{ secrets.DOCKER_AIO_CONTAINER_NAME }}` - name: Build and push `${{ secrets.DOCKER_AIO_CONTAINER_NAME }}`
uses: docker/build-push-action@v4 uses: docker/build-push-action@v4
@@ -62,5 +62,5 @@ jobs:
target: aio target: aio
push: true push: true
tags: | tags: |
${{ secrets.DOCKER_ORG_NAME }}/${{ secrets.DOCKER_AIO_CONTAINER_NAME }}:latest ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_AIO_CONTAINER_NAME }}:latest
${{ secrets.DOCKER_ORG_NAME }}/${{ secrets.DOCKER_AIO_CONTAINER_NAME }}:${{ github.ref_name }} ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_AIO_CONTAINER_NAME }}:${{ github.ref }}

View File

@@ -6,8 +6,8 @@ We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status, identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual nationality, personal appearance, race, religion, or sexual identity
identity and orientation. and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming, We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community. diverse, inclusive, and healthy community.
@@ -22,17 +22,17 @@ community include:
* Giving and gracefully accepting constructive feedback * Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes, * Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall * Focusing on what is best not just for us as individuals, but for the
community overall community
Examples of unacceptable behavior include: Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of * The use of sexualized language or imagery, and sexual attention or
any kind advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks * Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment * Public or private harassment
* Publishing others' private information, such as a physical or email address, * Publishing others' private information, such as a physical or email
without their explicit permission address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a * Other conduct which could reasonably be considered inappropriate in a
professional setting professional setting
@@ -82,15 +82,15 @@ behavior was inappropriate. A public apology may be requested.
### 2. Warning ### 2. Warning
**Community Impact**: A violation through a single incident or series of **Community Impact**: A violation through a single incident or series
actions. of actions.
**Consequence**: A warning with consequences for continued behavior. No **Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent like social media. Violating these terms may lead to a temporary or
ban. permanent ban.
### 3. Temporary Ban ### 3. Temporary Ban
@@ -106,27 +106,23 @@ Violating these terms may lead to a permanent ban.
### 4. Permanent Ban ### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community **Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals. individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the **Consequence**: A permanent ban from any sort of public interaction within
community. the community.
## Attribution ## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at version 2.0, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by Community Impact Guidelines were inspired by [Mozilla's code of conduct
[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. enforcement ladder](https://github.com/mozilla/diversity).
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org [homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity For answers to common questions about this code of conduct, see the FAQ at
[FAQ]: https://www.contributor-covenant.org/faq https://www.contributor-covenant.org/faq. Translations are available at
[translations]: https://www.contributor-covenant.org/translations https://www.contributor-covenant.org/translations.

190
README.md
View File

@@ -2,18 +2,23 @@
<a href="https://hoppscotch.io"> <a href="https://hoppscotch.io">
<img <img
src="https://avatars.githubusercontent.com/u/56705483" src="https://avatars.githubusercontent.com/u/56705483"
alt="Hoppscotch" alt="Hoppscotch Logo"
height="64" height="64"
/> />
</a> </a>
<h3> <br />
<p>
<h3>
<b>
Hoppscotch
</b>
</h3>
</p>
<p>
<b> <b>
Hoppscotch Open source API development ecosystem
</b> </b>
</h3> </p>
<b>
Open Source API Development Ecosystem
</b>
<p> <p>
[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen?logo=github)](CODE_OF_CONDUCT.md) [![Website](https://img.shields.io/website?url=https%3A%2F%2Fhoppscotch.io&logo=hoppscotch)](https://hoppscotch.io) [![Tests](https://github.com/hoppscotch/hoppscotch/actions/workflows/tests.yml/badge.svg)](https://github.com/hoppscotch/hoppscotch/actions) [![Tweet](https://img.shields.io/twitter/url?url=https%3A%2F%2Fhoppscotch.io%2F)](https://twitter.com/share?text=%F0%9F%91%BD%20Hoppscotch%20%E2%80%A2%20Open%20source%20API%20development%20ecosystem%20-%20Helps%20you%20create%20requests%20faster,%20saving%20precious%20time%20on%20development.&url=https://hoppscotch.io&hashtags=hoppscotch&via=hoppscotch_io) [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen?logo=github)](CODE_OF_CONDUCT.md) [![Website](https://img.shields.io/website?url=https%3A%2F%2Fhoppscotch.io&logo=hoppscotch)](https://hoppscotch.io) [![Tests](https://github.com/hoppscotch/hoppscotch/actions/workflows/tests.yml/badge.svg)](https://github.com/hoppscotch/hoppscotch/actions) [![Tweet](https://img.shields.io/twitter/url?url=https%3A%2F%2Fhoppscotch.io%2F)](https://twitter.com/share?text=%F0%9F%91%BD%20Hoppscotch%20%E2%80%A2%20Open%20source%20API%20development%20ecosystem%20-%20Helps%20you%20create%20requests%20faster,%20saving%20precious%20time%20on%20development.&url=https://hoppscotch.io&hashtags=hoppscotch&via=hoppscotch_io)
@@ -29,18 +34,23 @@
</p> </p>
<br /> <br />
<p> <p>
<a href="https://hoppscotch.io"> <a href="https://hoppscotch.io/#gh-light-mode-only" target="_blank">
<picture> <img
<source media="(prefers-color-scheme: dark)" srcset="./packages/hoppscotch-common/public/images/banner-dark.png"> src="./packages/hoppscotch-common/public/images/banner-light.png"
<source media="(prefers-color-scheme: light)" srcset="./packages/hoppscotch-common/public/images/banner-light.png"> alt="Hoppscotch"
<img alt="Hoppscotch" src="./packages/hoppscotch-common/public/images/banner-dark.png"> width="100%"
</picture> />
</a>
<a href="https://hoppscotch.io/#gh-dark-mode-only" target="_blank">
<img
src="./packages/hoppscotch-common/public/images/banner-dark.png"
alt="Hoppscotch"
width="100%"
/>
</a> </a>
</p> </p>
</div> </div>
_We highly recommend you take a look at the [**Hoppscotch Documentation**](https://docs.hoppscotch.io) to learn more about the app._
#### **Support** #### **Support**
[![Chat on Discord](https://img.shields.io/badge/chat-Discord-7289DA?logo=discord)](https://hoppscotch.io/discord) [![Chat on Telegram](https://img.shields.io/badge/chat-Telegram-2CA5E0?logo=telegram)](https://hoppscotch.io/telegram) [![Discuss on GitHub](https://img.shields.io/badge/discussions-GitHub-333333?logo=github)](https://github.com/hoppscotch/hoppscotch/discussions) [![Chat on Discord](https://img.shields.io/badge/chat-Discord-7289DA?logo=discord)](https://hoppscotch.io/discord) [![Chat on Telegram](https://img.shields.io/badge/chat-Telegram-2CA5E0?logo=telegram)](https://hoppscotch.io/telegram) [![Discuss on GitHub](https://img.shields.io/badge/discussions-GitHub-333333?logo=github)](https://github.com/hoppscotch/hoppscotch/discussions)
@@ -49,9 +59,9 @@ _We highly recommend you take a look at the [**Hoppscotch Documentation**](https
❤️ **Lightweight:** Crafted with minimalistic UI design. ❤️ **Lightweight:** Crafted with minimalistic UI design.
⚡️ **Fast:** Send requests and get responses in real time. ⚡️ **Fast:** Send requests and get/copy responses in real-time.
🗄️ **HTTP Methods:** Request methods define the type of action you are requesting to be performed. **HTTP Methods**
- `GET` - Requests retrieve resource information - `GET` - Requests retrieve resource information
- `POST` - The server creates a new entry in a database - `POST` - The server creates a new entry in a database
@@ -64,15 +74,17 @@ _We highly recommend you take a look at the [**Hoppscotch Documentation**](https
- `TRACE` - Performs a message loop-back test along the path to the target resource - `TRACE` - Performs a message loop-back test along the path to the target resource
- `<custom>` - Some APIs use custom request methods such as `LIST`. Type in your custom methods. - `<custom>` - Some APIs use custom request methods such as `LIST`. Type in your custom methods.
🌈 **Theming:** Customizable combinations for background, foreground, and accent colors — [customize now](https://hoppscotch.io/settings). 🌈 **Make it yours:** Customizable combinations for background, foreground, and accent colors — [customize now](https://hoppscotch.io/settings).
- Choose a theme: System preference, Light, Dark, and Black **Theming**
- Choose accent colors: Green, Teal, Blue, Indigo, Purple, Yellow, Orange, Red, and Pink
- Choose a theme: System (default), Light, Dark, and Black
- Choose accent color: Green (default), Teal, Blue, Indigo, Purple, Yellow, Orange, Red, and Pink
- Distraction-free Zen mode - Distraction-free Zen mode
_Customized themes are synced with your cloud/local session._ _Customized themes are synced with cloud / local session_
🔥 **PWA:** Install as a [Progressive Web App](https://web.dev/progressive-web-apps) on your device. 🔥 **PWA:** Install as a [PWA](https://web.dev/what-are-pwas/) on your device.
- Instant loading with Service Workers - Instant loading with Service Workers
- Offline support - Offline support
@@ -95,7 +107,7 @@ _Customized themes are synced with your cloud/local session._
📡 **Server-Sent Events:** Receive a stream of updates from a server over an HTTP connection without resorting to polling. 📡 **Server-Sent Events:** Receive a stream of updates from a server over an HTTP connection without resorting to polling.
🌩 **Socket.IO:** Send and Receive data with the SocketIO server. 🌩 **Socket.IO:** Send and Receive data with SocketIO server.
🦟 **MQTT:** Subscribe and Publish to topics of an MQTT Broker. 🦟 **MQTT:** Subscribe and Publish to topics of an MQTT Broker.
@@ -115,7 +127,7 @@ _Customized themes are synced with your cloud/local session._
- OAuth 2.0 - OAuth 2.0
- OIDC Access Token/PKCE - OIDC Access Token/PKCE
📢 **Headers:** Describes the format the body of your request is being sent in. 📢 **Headers:** Describes the format the body of your request is being sent as.
📫 **Parameters:** Use request parameters to set varying parts in simulated requests. 📫 **Parameters:** Use request parameters to set varying parts in simulated requests.
@@ -125,14 +137,14 @@ _Customized themes are synced with your cloud/local session._
- FormData, JSON, and many more - FormData, JSON, and many more
- Toggle between key-value and RAW input parameter list - Toggle between key-value and RAW input parameter list
📮 **Response:** Contains the status line, headers, and the message/response body. 👋 **Response:** Contains the status line, headers, and the message/response body.
- Copy the response to the clipboard - Copy response to clipboard
- Download the response as a file - Download response as a file
- View response headers - View response headers
- View raw and preview HTML, image, JSON, and XML responses - View raw and preview of HTML, image, JSON, XML responses
**History:** Request entries are synced with your cloud/local session storage. **History:** Request entries are synced with cloud / local session storage to restore with a single click.
📁 **Collections:** Keep your API requests organized with collections and folders. Reuse them with a single click. 📁 **Collections:** Keep your API requests organized with collections and folders. Reuse them with a single click.
@@ -140,32 +152,7 @@ _Customized themes are synced with your cloud/local session._
- Nested folders - Nested folders
- Export and import as a file or GitHub gist - Export and import as a file or GitHub gist
_Collections are synced with your cloud/local session storage._ _Collections are synced with cloud / local session storage_
📜 **Pre-Request Scripts:** Snippets of code associated with a request that is executed before the request is sent.
- Set environment variables
- Include timestamp in the request headers
- Send a random alphanumeric string in the URL parameters
- Any JavaScript functions
👨‍👩‍👧‍👦 **Teams:** Helps you collaborate across your teams to design, develop, and test APIs faster.
- Create unlimited teams
- Create unlimited shared collections
- Create unlimited team members
- Role-based access control
- Cloud sync
- Multiple devices
👥 **Workspaces:** Organize your personal and team collections environments into workspaces. Easily switch between workspaces to manage multiple projects.
- Create unlimited workspaces
- Switch between personal and team workspaces
⌨️ **Keyboard Shortcuts:** Optimized for efficiency.
> **[Read our documentation on Keyboard Shortcuts](https://docs.hoppscotch.io/documentation/features/shortcuts)**
🌐 **Proxy:** Enable Proxy Mode from Settings to access blocked APIs. 🌐 **Proxy:** Enable Proxy Mode from Settings to access blocked APIs.
@@ -174,31 +161,60 @@ _Collections are synced with your cloud/local session storage._
- Access APIs served in non-HTTPS (`http://`) endpoints - Access APIs served in non-HTTPS (`http://`) endpoints
- Use your Proxy URL - Use your Proxy URL
_Official proxy server is hosted by Hoppscotch - **[GitHub](https://github.com/hoppscotch/proxyscotch)** - **[Privacy Policy](https://docs.hoppscotch.io/support/privacy)**._ _Official proxy server is hosted by Hoppscotch - **[GitHub](https://github.com/hoppscotch/proxyscotch)** - **[Privacy Policy](https://docs.hoppscotch.io/support/privacy)**_
📜 **Pre-Request Scripts β:** Snippets of code associated with a request that is executed before the request is sent.
- Set environment variables
- Include timestamp in the request headers
- Send a random alphanumeric string in the URL parameters
- Any JavaScript functions
📄 **API Documentation:** Create and share dynamic API documentation easily, quickly.
1. Add your requests to Collections and Folders
2. Export Collections and easily share your APIs with the rest of your team
3. Import Collections and Generate Documentation on-the-go
⌨️ **Keyboard Shortcuts:** Optimized for efficiency.
> **[Read our documentation on Keyboard Shortcuts](https://docs.hoppscotch.io/documentation/features/shortcuts)**
🌎 **i18n:** Experience the app in your language. 🌎 **i18n:** Experience the app in your language.
Help us to translate Hoppscotch. Please read [`TRANSLATIONS`](TRANSLATIONS.md) for details on our [`CODE OF CONDUCT`](CODE_OF_CONDUCT.md) and the process for submitting pull requests to us. Help us to translate Hoppscotch. Please read [`TRANSLATIONS`](TRANSLATIONS.md) for details on our [`CODE OF CONDUCT`](CODE_OF_CONDUCT.md), and the process for submitting pull requests to us.
☁️ **Auth + Sync:** Sign in and sync your data in real-time across all your devices. 📦 **Add-ons:** Official add-ons for hoppscotch.
**Sign in with:** - **[Proxy](https://github.com/hoppscotch/proxyscotch)** - A simple proxy server created for Hoppscotch
- **[CLI β](https://github.com/hoppscotch/hopp-cli)** - A CLI solution for Hoppscotch
- **[Browser Extensions](https://github.com/hoppscotch/hoppscotch-extension)** - Browser extensions that simplifies access to Hoppscotch
[![Firefox](https://raw.github.com/alrra/browser-logos/master/src/firefox/firefox_16x16.png) **Firefox**](https://addons.mozilla.org/en-US/firefox/addon/hoppscotch) &nbsp;|&nbsp; [![Chrome](https://raw.github.com/alrra/browser-logos/master/src/chrome/chrome_16x16.png) **Chrome**](https://chrome.google.com/webstore/detail/hoppscotch-extension-for-c/amknoiejhlmhancpahfcfcfhllgkpbld)
> **Extensions fixes `CORS` issues.**
- **[Hopp-Doc-Gen](https://github.com/hoppscotch/hopp-doc-gen)** - An API doc generator CLI for Hoppscotch
_Add-ons are developed and maintained under **[Hoppscotch Organization](https://github.com/hoppscotch)**._
☁️ **Auth + Sync:** Sign in and sync your data in real-time.
**Sign in with**
- GitHub - GitHub
- Google - Google
- Microsoft - Microsoft
- Email - Email
- SSO (Single Sign-On)[^EE]
**🔄 Synchronize your data:** Handoff to continue tasks on your other devices. **Synchronize your data**
- Workspaces
- History - History
- Collections - Collections
- Environments - Environments
- Settings - Settings
**Post-Request Tests:** Write tests associated with a request that is executed after the request's response. **Post-Request Tests β:** Write tests associated with a request that is executed after the request's response.
- Check the status code as an integer - Check the status code as an integer
- Filter response headers - Filter response headers
@@ -206,7 +222,7 @@ Help us to translate Hoppscotch. Please read [`TRANSLATIONS`](TRANSLATIONS.md) f
- Set environment variables - Set environment variables
- Write JavaScript code - Write JavaScript code
🌱 **Environments:** Environment variables allow you to store and reuse values in your requests and scripts. 🌱 **Environments** : Environment variables allow you to store and reuse values in your requests and scripts.
- Unlimited environments and variables - Unlimited environments and variables
- Initialize through the pre-request script - Initialize through the pre-request script
@@ -225,31 +241,22 @@ Help us to translate Hoppscotch. Please read [`TRANSLATIONS`](TRANSLATIONS.md) f
</details> </details>
👨‍👩‍👧‍👦 **Teams β:** Helps you collaborate across your team to design, develop, and test APIs faster.
- Unlimited teams
- Unlimited shared collections
- Unlimited team members
- Role-based access control
- Cloud sync
- Multiple devices
🚚 **Bulk Edit:** Edit key-value pairs in bulk. 🚚 **Bulk Edit:** Edit key-value pairs in bulk.
- Entries are separated by newline - Entries are separated by newline
- Keys and values are separated by `:` - Keys and values are separated by `:`
- Prepend `#` to any row you want to add but keep disabled - Prepend `#` to any row you want to add but keep disabled
🎛️ **Admin dashboard:** Manage your team and invite members. **For more features, please read our [documentation](https://docs.hoppscotch.io).**
- Insights
- Manage users
- Manage teams
📦 **Add-ons:** Official add-ons for hoppscotch.
- **[Hoppscotch CLI](https://github.com/hoppscotch/hopp-cli)** - Command-line interface for Hoppscotch.
- **[Proxy](https://github.com/hoppscotch/proxyscotch)** - A simple proxy server created for Hoppscotch.
- **[Browser Extensions](https://github.com/hoppscotch/hoppscotch-extension)** - Browser extensions that enhance your Hoppscotch experience.
[![Firefox](https://raw.github.com/alrra/browser-logos/master/src/firefox/firefox_16x16.png) **Firefox**](https://addons.mozilla.org/en-US/firefox/addon/hoppscotch) &nbsp;|&nbsp; [![Chrome](https://raw.github.com/alrra/browser-logos/master/src/chrome/chrome_16x16.png) **Chrome**](https://chrome.google.com/webstore/detail/hoppscotch-extension-for-c/amknoiejhlmhancpahfcfcfhllgkpbld)
> **Extensions fix `CORS` issues.**
_Add-ons are developed and maintained under **[Hoppscotch Organization](https://github.com/hoppscotch)**._
**For a complete list of features, please read our [documentation](https://docs.hoppscotch.io).**
## **Demo** ## **Demo**
@@ -261,9 +268,18 @@ _Add-ons are developed and maintained under **[Hoppscotch Organization](https://
2. Click "Send" to simulate the request 2. Click "Send" to simulate the request
3. View the response 3. View the response
## **Built with**
- [HTML](https://developer.mozilla.org/en-US/docs/Web/HTML)
- [CSS](https://developer.mozilla.org/en-US/docs/Web/CSS), [SCSS](https://sass-lang.com), [Windi CSS](https://windicss.org)
- [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript)
- [TypeScript](https://www.typescriptlang.org)
- [Vue](https://vuejs.org)
- [Vite](https://vitejs.dev)
## **Developing** ## **Developing**
Follow our [self-hosting documentation](https://docs.hoppscotch.io/documentation/self-host/getting-started) to get started with the development environment. Follow our [self-hosting guide](https://docs.hoppscotch.io/documentation/self-host/getting-started) to get started with the development environment.
## **Contributing** ## **Contributing**
@@ -281,7 +297,7 @@ See the [`CHANGELOG`](CHANGELOG.md) file for details.
## **Authors** ## **Authors**
This project owes its existence to the collective efforts of all those who contribute — [contribute now](CONTRIBUTING.md). This project exists thanks to all the people who contribute — [contribute](CONTRIBUTING.md).
<div align="center"> <div align="center">
<a href="https://github.com/hoppscotch/hoppscotch/graphs/contributors"> <a href="https://github.com/hoppscotch/hoppscotch/graphs/contributors">
@@ -293,6 +309,4 @@ This project owes its existence to the collective efforts of all those who contr
## **License** ## **License**
This project is licensed under the [MIT License](https://opensource.org/licenses/MIT) see the [`LICENSE`](LICENSE) file for details. This project is licensed under the [MIT License](https://opensource.org/licenses/MIT) - see the [`LICENSE`](LICENSE) file for details.
[^EE]: Enterprise edition feature. [Learn more](https://docs.hoppscotch.io/documentation/self-host/getting-started).

View File

@@ -2,9 +2,8 @@
This document outlines security procedures and general policies for the Hoppscotch project. This document outlines security procedures and general policies for the Hoppscotch project.
- [Security Policy](#security-policy) 1. [Reporting a security vulnerability](#reporting-a-security-vulnerability)
- [Reporting a security vulnerability](#reporting-a-security-vulnerability) 3. [Incident response process](#incident-response-process)
- [Incident response process](#incident-response-process)
## Reporting a security vulnerability ## Reporting a security vulnerability

View File

@@ -9,24 +9,26 @@ Before you start working on a new language, please look through the [open pull r
if there is no existing translation, you can create a new one by following these steps: if there is no existing translation, you can create a new one by following these steps:
1. **[Fork the repository](https://github.com/hoppscotch/hoppscotch/fork).** 1. **[Fork the repository](https://github.com/hoppscotch/hoppscotch/fork).**
2. **Checkout the `main` branch for latest translations.** 2. **Checkout the `i18n` branch for latest translations.**
3. **Create a new branch for your translation with base branch `main`.** 3. **Create a new branch for your translation with base branch `i18n`.**
4. **Create target language file in the [`/packages/hoppscotch-common/locales`](https://github.com/hoppscotch/hoppscotch/tree/main/packages/hoppscotch-common/locales) directory.** 4. **Create target language file in the [`/packages/hoppscotch-common/locales`](https://github.com/hoppscotch/hoppscotch/tree/main/packages/hoppscotch-common/locales) directory.**
5. **Copy the contents of the source file [`/packages/hoppscotch-common/locales/en.json`](https://github.com/hoppscotch/hoppscotch/blob/main/packages/hoppscotch-common/locales/en.json) to the target language file.** 5. **Copy the contents of the source file [`/packages/hoppscotch-common/locales/en.json`](https://github.com/hoppscotch/hoppscotch/blob/main/packages/hoppscotch-common/locales/en.json) to the target language file.**
6. **Translate the strings in the target language file.** 6. **Translate the strings in the target language file.**
7. **Add your language entry to [`/packages/hoppscotch-common/languages.json`](https://github.com/hoppscotch/hoppscotch/blob/main/packages/hoppscotch-common/languages.json).** 7. **Add your language entry to [`/packages/hoppscotch-common/languages.json`](https://github.com/hoppscotch/hoppscotch/blob/main/packages/hoppscotch-common/languages.json).**
8. **Save and commit changes.** 8. **Save & commit changes.**
9. **Send a pull request.** 9. **Send a pull request.**
_You may send a pull request before all steps above are complete: e.g., you may want to ask for help with translations, or getting tests to pass. However, your pull request will not be merged until all steps above are complete._ _You may send a pull request before all steps above are complete: e.g., you may want to ask for help with translations, or getting tests to pass. However, your pull request will not be merged until all steps above are complete._
`i18n` branch will be merged into `main` branch once every week.
Completing an initial translation of the whole site is a fairly large task. One way to break that task up is to work with other translators through pull requests on your fork. You can also [add collaborators to your fork](https://help.github.com/en/github/setting-up-and-managing-your-github-user-account/inviting-collaborators-to-a-personal-repository) if you'd like to invite other translators to commit directly to your fork and share responsibility for merging pull requests. Completing an initial translation of the whole site is a fairly large task. One way to break that task up is to work with other translators through pull requests on your fork. You can also [add collaborators to your fork](https://help.github.com/en/github/setting-up-and-managing-your-github-user-account/inviting-collaborators-to-a-personal-repository) if you'd like to invite other translators to commit directly to your fork and share responsibility for merging pull requests.
## Updating a translation ## Updating a translation
### Corrections ### Corrections
If you notice spelling or grammar errors, typos, or opportunities for better phrasing, open a pull request with your suggested fix. If you see a problem that you aren't sure of or don't have time to fix, [open an issue](https://github.com/hoppscotch/hoppscotch/issues/new/choose). If you notice spelling or grammar errors, typos, or opportunities for better phrasing, open a pull request with your suggested fix. If you see a problem that you aren't sure of or don't have time to fix, open an issue.
### Broken links ### Broken links

View File

@@ -20,7 +20,7 @@ services:
- PORT=3170 - PORT=3170
volumes: volumes:
# Uncomment the line below when modifying code. Only applicable when using the "dev" target. # Uncomment the line below when modifying code. Only applicable when using the "dev" target.
- ./packages/hoppscotch-backend/:/usr/src/app/packages/hoppscotch-backend # - ./packages/hoppscotch-backend/:/usr/src/app
- /usr/src/app/node_modules/ - /usr/src/app/node_modules/
depends_on: depends_on:
hoppscotch-db: hoppscotch-db:

View File

@@ -21,8 +21,7 @@
"test:cov": "jest --coverage", "test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json", "test:e2e": "jest --config ./test/jest-e2e.json",
"do-test": "pnpm run test", "do-test": "pnpm run test"
"seed": "node --loader ts-node/esm prisma/seed.ts"
}, },
"dependencies": { "dependencies": {
"@nestjs-modules/mailer": "^1.8.1", "@nestjs-modules/mailer": "^1.8.1",
@@ -58,7 +57,6 @@
"passport-jwt": "^4.0.1", "passport-jwt": "^4.0.1",
"passport-local": "^1.0.0", "passport-local": "^1.0.0",
"passport-microsoft": "^1.0.0", "passport-microsoft": "^1.0.0",
"pg": "^8.11.3",
"prisma": "^4.16.2", "prisma": "^4.16.2",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",

View File

@@ -1,2 +0,0 @@
-- CreateIndex
CREATE INDEX "TeamMember_userUid_idx" ON "TeamMember"("userUid");

View File

@@ -26,7 +26,6 @@ model TeamMember {
team Team @relation(fields: [teamID], references: [id], onDelete: Cascade) team Team @relation(fields: [teamID], references: [id], onDelete: Cascade)
@@unique([teamID, userUid]) @@unique([teamID, userUid])
@@index([userUid])
} }
model TeamInvitation { model TeamInvitation {

View File

@@ -1,58 +0,0 @@
import { PrismaClient, TeamMemberRole } from '@prisma/client';
const prisma = new PrismaClient();
const noOfUsers = 600000;
const getAllUser = async () => {
const users = await prisma.user.findMany();
return users;
};
const createUsers = async () => {
for (let i = 1; i <= noOfUsers; i++) {
try {
await prisma.user.create({
data: {
email: `${i}@gmail.com`,
},
});
} catch (_) {}
}
};
const createTeams = async () => {
const users = await getAllUser();
for (let i = 0; i < users.length; i++) {
try {
await prisma.team.create({
data: {
name: `Team ${i + 1}`,
members: {
create: {
userUid: users[i].uid,
role: TeamMemberRole.OWNER,
},
},
},
});
} catch (_) {}
}
};
async function main() {
console.log('Seeding...');
await createUsers();
await createTeams();
}
main()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});

View File

@@ -20,7 +20,6 @@ import { ShortcodeModule } from './shortcode/shortcode.module';
import { COOKIES_NOT_FOUND } from './errors'; import { COOKIES_NOT_FOUND } from './errors';
import { ThrottlerModule } from '@nestjs/throttler'; import { ThrottlerModule } from '@nestjs/throttler';
import { AppController } from './app.controller'; import { AppController } from './app.controller';
import { DbModule } from './db/db.module';
@Module({ @Module({
imports: [ imports: [
@@ -67,7 +66,6 @@ import { DbModule } from './db/db.module';
ttl: +process.env.RATE_LIMIT_TTL, ttl: +process.env.RATE_LIMIT_TTL,
limit: +process.env.RATE_LIMIT_MAX, limit: +process.env.RATE_LIMIT_MAX,
}), }),
DbModule,
UserModule, UserModule,
AuthModule, AuthModule,
AdminModule, AdminModule,

View File

@@ -1 +0,0 @@
export const PG_CONNECTION = 'PG_CONNECTION';

View File

@@ -1,21 +0,0 @@
import { Global, Module } from '@nestjs/common';
import { Pool } from 'pg';
import { PG_CONNECTION } from 'src/constants';
const dbProvider = {
provide: PG_CONNECTION,
useValue: new Pool({
user: 'postgres',
host: 'hoppscotch-db',
database: 'hoppscotch',
password: 'testpass',
port: 5432,
}),
};
@Global()
@Module({
providers: [dbProvider],
exports: [dbProvider],
})
export class DbModule {}

View File

@@ -7,11 +7,7 @@ export class PrismaService
implements OnModuleInit, OnModuleDestroy implements OnModuleInit, OnModuleDestroy
{ {
constructor() { constructor() {
super( super();
{
log: ['query', 'info', 'warn', 'error'],
}
);
} }
async onModuleInit() { async onModuleInit() {
await this.$connect(); await this.$connect();

View File

@@ -22,7 +22,6 @@ import { throwErr } from 'src/utils';
import { AuthUser } from 'src/types/AuthUser'; import { AuthUser } from 'src/types/AuthUser';
import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard'; import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard';
import { SkipThrottle } from '@nestjs/throttler'; import { SkipThrottle } from '@nestjs/throttler';
import { cons } from 'fp-ts/lib/ReadonlyNonEmptyArray';
@UseGuards(GqlThrottlerGuard) @UseGuards(GqlThrottlerGuard)
@Resolver(() => Team) @Resolver(() => Team)
@@ -56,13 +55,8 @@ export class TeamResolver {
description: 'Returns the list of members of a team', description: 'Returns the list of members of a team',
complexity: 10, complexity: 10,
}) })
async teamMembers(@Parent() team: Team): Promise<TeamMember[]> { teamMembers(@Parent() team: Team): Promise<TeamMember[]> {
const startR = Date.now(); return this.teamService.getTeamMembers(team.id);
const members = await this.teamService.getTeamMembers(team.id);
const endR = Date.now();
console.log('response generation: (teamMembers)', endR - startR, 'ms');
return members;
} }
@ResolveField(() => TeamMemberRole, { @ResolveField(() => TeamMemberRole, {
@@ -70,61 +64,41 @@ export class TeamResolver {
nullable: true, nullable: true,
}) })
@UseGuards(GqlAuthGuard) @UseGuards(GqlAuthGuard)
async myRole( myRole(
@Parent() team: Team, @Parent() team: Team,
@GqlUser() user: AuthUser, @GqlUser() user: AuthUser,
): Promise<TeamMemberRole | null> { ): Promise<TeamMemberRole | null> {
const startR = Date.now(); return this.teamService.getRoleOfUserInTeam(team.id, user.uid);
const role = await this.teamService.getRoleOfUserInTeam(team.id, user.uid);
const endR = Date.now();
console.log('response generation: (myRole)', endR - startR, 'ms');
return role;
} }
@ResolveField(() => Int, { @ResolveField(() => Int, {
description: 'The number of users with the OWNER role in the team', description: 'The number of users with the OWNER role in the team',
}) })
async ownersCount(@Parent() team: Team): Promise<number> { ownersCount(@Parent() team: Team): Promise<number> {
const startR = Date.now(); return this.teamService.getCountOfUsersWithRoleInTeam(
const count = await this.teamService.getCountOfUsersWithRoleInTeam(
team.id, team.id,
TeamMemberRole.OWNER, TeamMemberRole.OWNER,
); );
const endR = Date.now();
console.log('response generation: (ownersCount)', endR - startR, 'ms');
return count;
} }
@ResolveField(() => Int, { @ResolveField(() => Int, {
description: 'The number of users with the EDITOR role in the team', description: 'The number of users with the EDITOR role in the team',
}) })
async editorsCount(@Parent() team: Team): Promise<number> { editorsCount(@Parent() team: Team): Promise<number> {
const startR = Date.now(); return this.teamService.getCountOfUsersWithRoleInTeam(
const count = await this.teamService.getCountOfUsersWithRoleInTeam(
team.id, team.id,
TeamMemberRole.EDITOR, TeamMemberRole.EDITOR,
); );
const endR = Date.now();
console.log('response generation: (editorsCount)', endR - startR, 'ms');
return count;
} }
@ResolveField(() => Int, { @ResolveField(() => Int, {
description: 'The number of users with the VIEWER role in the team', description: 'The number of users with the VIEWER role in the team',
}) })
async viewersCount(@Parent() team: Team): Promise<number> { viewersCount(@Parent() team: Team): Promise<number> {
const startR = Date.now(); return this.teamService.getCountOfUsersWithRoleInTeam(
const count = await this.teamService.getCountOfUsersWithRoleInTeam(
team.id, team.id,
TeamMemberRole.VIEWER, TeamMemberRole.VIEWER,
); );
const endR = Date.now();
console.log('response generation: (viewersCount)', endR - startR, 'ms');
return count;
} }
// Query // Query
@@ -132,7 +106,7 @@ export class TeamResolver {
description: 'List of teams that the executing user belongs to.', description: 'List of teams that the executing user belongs to.',
}) })
@UseGuards(GqlAuthGuard) @UseGuards(GqlAuthGuard)
async myTeams( myTeams(
@GqlUser() user: AuthUser, @GqlUser() user: AuthUser,
@Args({ @Args({
name: 'cursor', name: 'cursor',
@@ -143,15 +117,7 @@ export class TeamResolver {
}) })
cursor?: string, cursor?: string,
): Promise<Team[]> { ): Promise<Team[]> {
const startR = Date.now(); return this.teamService.getTeamsOfUser(user.uid, cursor ?? null);
const teams = await this.teamService.getTeamsOfUser(
user.uid,
cursor ?? null,
);
const endR = Date.now();
console.log('response generation: (myTeams)', endR - startR, 'ms');
return teams;
} }
@Query(() => Team, { @Query(() => Team, {
@@ -164,7 +130,7 @@ export class TeamResolver {
TeamMemberRole.EDITOR, TeamMemberRole.EDITOR,
TeamMemberRole.OWNER, TeamMemberRole.OWNER,
) )
async team( team(
@Args({ @Args({
name: 'teamID', name: 'teamID',
type: () => ID, type: () => ID,
@@ -172,12 +138,7 @@ export class TeamResolver {
}) })
teamID: string, teamID: string,
): Promise<Team | null> { ): Promise<Team | null> {
const startR = Date.now(); return this.teamService.getTeamWithID(teamID);
const team = await this.teamService.getTeamWithID(teamID);
const endR = Date.now();
console.log('response generation: (team)', endR - startR, 'ms');
return team;
} }
// Mutation // Mutation
@@ -190,11 +151,7 @@ export class TeamResolver {
@Args({ name: 'name', description: 'Displayed name of the team' }) @Args({ name: 'name', description: 'Displayed name of the team' })
name: string, name: string,
): Promise<Team> { ): Promise<Team> {
const startR = Date.now();
const team = await this.teamService.createTeam(name, user.uid); const team = await this.teamService.createTeam(name, user.uid);
const endR = Date.now();
console.log('response generation: (createTeam)', endR - startR, 'ms');
if (E.isLeft(team)) throwErr(team.left); if (E.isLeft(team)) throwErr(team.left);
return team.right; return team.right;
} }
@@ -212,11 +169,7 @@ export class TeamResolver {
}) })
teamID: string, teamID: string,
): Promise<boolean> { ): Promise<boolean> {
const startR = Date.now();
const isUserLeft = await this.teamService.leaveTeam(teamID, user.uid); const isUserLeft = await this.teamService.leaveTeam(teamID, user.uid);
const endR = Date.now();
console.log('response generation: (leaveTeam)', endR - startR, 'ms');
if (E.isLeft(isUserLeft)) throwErr(isUserLeft.left); if (E.isLeft(isUserLeft)) throwErr(isUserLeft.left);
return isUserLeft.right; return isUserLeft.right;
} }
@@ -241,11 +194,7 @@ export class TeamResolver {
}) })
userUid: string, userUid: string,
): Promise<boolean> { ): Promise<boolean> {
const startR = Date.now();
const isRemoved = await this.teamService.leaveTeam(teamID, userUid); const isRemoved = await this.teamService.leaveTeam(teamID, userUid);
const endR = Date.now();
console.log('response generation: (removeTeamMember)', endR - startR, 'ms');
if (E.isLeft(isRemoved)) throwErr(isRemoved.left); if (E.isLeft(isRemoved)) throwErr(isRemoved.left);
return isRemoved.right; return isRemoved.right;
} }
@@ -261,11 +210,7 @@ export class TeamResolver {
@Args({ name: 'newName', description: 'The updated name of the team' }) @Args({ name: 'newName', description: 'The updated name of the team' })
newName: string, newName: string,
): Promise<Team> { ): Promise<Team> {
const startR = Date.now();
const team = await this.teamService.renameTeam(teamID, newName); const team = await this.teamService.renameTeam(teamID, newName);
const endR = Date.now();
console.log('response generation: (renameTeam)', endR - startR, 'ms');
if (E.isLeft(team)) throwErr(team.left); if (E.isLeft(team)) throwErr(team.left);
return team.right; return team.right;
} }
@@ -279,11 +224,7 @@ export class TeamResolver {
@Args({ name: 'teamID', description: 'ID of the team', type: () => ID }) @Args({ name: 'teamID', description: 'ID of the team', type: () => ID })
teamID: string, teamID: string,
): Promise<boolean> { ): Promise<boolean> {
const startR = Date.now();
const isDeleted = await this.teamService.deleteTeam(teamID); const isDeleted = await this.teamService.deleteTeam(teamID);
const endR = Date.now();
console.log('response generation: (deleteTeam)', endR - startR, 'ms');
if (E.isLeft(isDeleted)) throwErr(isDeleted.left); if (E.isLeft(isDeleted)) throwErr(isDeleted.left);
return isDeleted.right; return isDeleted.right;
} }
@@ -313,19 +254,11 @@ export class TeamResolver {
}) })
newRole: TeamMemberRole, newRole: TeamMemberRole,
): Promise<TeamMember> { ): Promise<TeamMember> {
const startR = Date.now();
const teamMember = await this.teamService.updateTeamMemberRole( const teamMember = await this.teamService.updateTeamMemberRole(
teamID, teamID,
userUid, userUid,
newRole, newRole,
); );
const endR = Date.now();
console.log(
'response generation: (updateTeamMemberRole)',
endR - startR,
'ms',
);
if (E.isLeft(teamMember)) throwErr(teamMember.left); if (E.isLeft(teamMember)) throwErr(teamMember.left);
return teamMember.right; return teamMember.right;
} }

View File

@@ -1,4 +1,4 @@
import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; import { Injectable, OnModuleInit } from '@nestjs/common';
import { TeamMember, TeamMemberRole, Team } from './team.model'; import { TeamMember, TeamMemberRole, Team } from './team.model';
import { PrismaService } from '../prisma/prisma.service'; import { PrismaService } from '../prisma/prisma.service';
import { TeamMember as DbTeamMember } from '@prisma/client'; import { TeamMember as DbTeamMember } from '@prisma/client';
@@ -23,8 +23,6 @@ import * as T from 'fp-ts/Task';
import * as A from 'fp-ts/Array'; import * as A from 'fp-ts/Array';
import { throwErr } from 'src/utils'; import { throwErr } from 'src/utils';
import { AuthUser } from '../types/AuthUser'; import { AuthUser } from '../types/AuthUser';
import { PG_CONNECTION } from 'src/constants';
import { Client } from 'pg';
@Injectable() @Injectable()
export class TeamService implements UserDataHandler, OnModuleInit { export class TeamService implements UserDataHandler, OnModuleInit {
@@ -32,11 +30,8 @@ export class TeamService implements UserDataHandler, OnModuleInit {
private readonly prisma: PrismaService, private readonly prisma: PrismaService,
private readonly userService: UserService, private readonly userService: UserService,
private readonly pubsub: PubSubService, private readonly pubsub: PubSubService,
@Inject(PG_CONNECTION) private conn: Client,
) {} ) {}
enableRawSql: boolean = false;
onModuleInit() { onModuleInit() {
this.userService.registerUserDataHandler(this); this.userService.registerUserDataHandler(this);
} }
@@ -57,37 +52,12 @@ export class TeamService implements UserDataHandler, OnModuleInit {
teamID: string, teamID: string,
role: TeamMemberRole, role: TeamMemberRole,
): Promise<number> { ): Promise<number> {
if (this.enableRawSql) { return await this.prisma.teamMember.count({
const startQ = Date.now();
const count = await this.conn.query(
`SELECT COUNT(*) FROM "TeamMember" WHERE "teamID" = '${teamID}' AND "role" = '${role}';`,
);
const endQ = Date.now();
console.log(
'getCountOfUsersWithRoleInTeam >>>>>>>>>>',
endQ - startQ,
'ms >>>>>',
count.rows,
);
return count.rows[0].count;
}
const startQ = Date.now();
const count = await this.prisma.teamMember.count({
where: { where: {
teamID, teamID,
role, role,
}, },
}); });
const endQ = Date.now();
console.log(
'getCountOfUsersWithRoleInTeam >>>>>>>>>>',
endQ - startQ,
'ms >>>>>',
count,
);
return count;
} }
async addMemberToTeamWithEmail( async addMemberToTeamWithEmail(
@@ -107,11 +77,6 @@ export class TeamService implements UserDataHandler, OnModuleInit {
uid: string, uid: string,
role: TeamMemberRole, role: TeamMemberRole,
): Promise<TeamMember> { ): Promise<TeamMember> {
const tm = await this.conn.query(
`INSERT INTO "TeamMember" (id, userUid, teamID, role) VALUES ('${new Date().toISOString()}', '${uid}', '${teamID}', '${role}') RETURNING *;`,
);
console.log('addMemberToTeam >>>>>>>>>>', tm.rows[0]);
const teamMember = await this.prisma.teamMember.create({ const teamMember = await this.prisma.teamMember.create({
data: { data: {
userUid: uid, userUid: uid,
@@ -136,31 +101,6 @@ export class TeamService implements UserDataHandler, OnModuleInit {
} }
async deleteTeam(teamID: string): Promise<E.Left<string> | E.Right<boolean>> { async deleteTeam(teamID: string): Promise<E.Left<string> | E.Right<boolean>> {
if (this.enableRawSql) {
const startQ = Date.now();
const t = await this.conn.query(
`SELECT * FROM "Team" WHERE "id" = '${teamID}'`,
);
if (t.rows.length === 0) return E.left(TEAM_INVALID_ID);
await this.conn.query(
`DELETE FROM "TeamMember" WHERE "teamID" = '${teamID}' RETURNING *`,
);
await this.conn.query(`DELETE FROM "Team" WHERE "id" = '${teamID}'`);
const endQ = Date.now();
console.log(
'deleteTeam >>>>>>>>>>',
endQ - startQ,
'ms',
'>>>>>',
t.rows[0],
);
return E.right(true);
}
const startQ = Date.now();
const team = await this.prisma.team.findUnique({ const team = await this.prisma.team.findUnique({
where: { where: {
id: teamID, id: teamID,
@@ -179,8 +119,6 @@ export class TeamService implements UserDataHandler, OnModuleInit {
id: teamID, id: teamID,
}, },
}); });
const endQ = Date.now();
console.log('deleteTeam >>>>>>>>>>', endQ - startQ, 'ms', '>>>>>', team);
return E.right(true); return E.right(true);
} }
@@ -197,26 +135,6 @@ export class TeamService implements UserDataHandler, OnModuleInit {
const isValidTitle = this.validateTeamName(newName); const isValidTitle = this.validateTeamName(newName);
if (E.isLeft(isValidTitle)) return isValidTitle; if (E.isLeft(isValidTitle)) return isValidTitle;
if (this.enableRawSql) {
const startQ = Date.now();
const ut = await this.conn.query(
`UPDATE "Team" SET "name" = '${newName}' WHERE "id" = '${teamID}' RETURNING *`,
);
const endQ = Date.now();
console.log(
'renameTeam >>>>>>>>>>',
endQ - startQ,
'ms',
'>>>>>',
ut.rows[0],
);
return E.right(<Team>{
id: ut.rows[0].id,
name: ut.rows[0].name,
});
}
try { try {
const updatedTeam = await this.prisma.team.update({ const updatedTeam = await this.prisma.team.update({
where: { where: {
@@ -238,48 +156,6 @@ export class TeamService implements UserDataHandler, OnModuleInit {
userUid: string, userUid: string,
newRole: TeamMemberRole, newRole: TeamMemberRole,
): Promise<E.Left<string> | E.Right<TeamMember>> { ): Promise<E.Left<string> | E.Right<TeamMember>> {
if (this.enableRawSql) {
const startQ = Date.now();
const oc = await this.conn.query(
`SELECT COUNT(*) FROM "TeamMember" WHERE "teamID" = '${teamID}' AND "role" = '${TeamMemberRole.OWNER}';`,
);
const tm = await this.conn.query(
`SELECT * FROM "TeamMember" WHERE "teamID" = '${teamID}' AND "userUid" = '${userUid}'`,
);
if (tm.rows.length === 0) return E.left(TEAM_MEMBER_NOT_FOUND);
const ownerCount = oc.rows[0].count;
if (
tm.rows[0].role === TeamMemberRole.OWNER &&
newRole != TeamMemberRole.OWNER &&
ownerCount === 1
) {
return E.left(TEAM_ONLY_ONE_OWNER);
}
const utm = await this.conn.query(
`UPDATE "teamMember" SET "role" = '${newRole}' WHERE "teamID" = '${teamID}' AND "userUid" = '${userUid}'`,
);
const endQ = Date.now();
console.log(
'updateTeamMemberRole >>>>>>>>>>',
endQ - startQ,
'ms',
'>>>>>',
utm.rows[0],
);
const updatedMember: TeamMember = {
membershipID: utm.rows[0].id,
userUid: utm.rows[0].userUid,
role: TeamMemberRole[utm.rows[0].role],
};
this.pubsub.publish(`team/${teamID}/member_updated`, updatedMember);
return E.right(updatedMember);
}
const startQ = Date.now();
const ownerCount = await this.prisma.teamMember.count({ const ownerCount = await this.prisma.teamMember.count({
where: { where: {
teamID, teamID,
@@ -316,14 +192,6 @@ export class TeamService implements UserDataHandler, OnModuleInit {
role: newRole, role: newRole,
}, },
}); });
const endQ = Date.now();
console.log(
'updateTeamMemberRole >>>>>>>>>>',
endQ - startQ,
'ms',
'>>>>>',
result,
);
const updatedMember: TeamMember = { const updatedMember: TeamMember = {
membershipID: result.id, membershipID: result.id,
@@ -340,30 +208,6 @@ export class TeamService implements UserDataHandler, OnModuleInit {
teamID: string, teamID: string,
userUid: string, userUid: string,
): Promise<E.Left<string> | E.Right<boolean>> { ): Promise<E.Left<string> | E.Right<boolean>> {
if (this.enableRawSql) {
const oc = await this.conn.query(
`SELECT COUNT(*) FROM "TeamMember" WHERE "teamID" = '${teamID}' AND "role" = '${TeamMemberRole.OWNER}';`,
);
const ownerCount = oc.rows[0].count;
console.log('leaveTeam >>>>>>>>>>', oc.rows);
const member = await this.getTeamMember(teamID, userUid);
if (!member) return E.left(TEAM_INVALID_ID_OR_USER);
if (ownerCount === 1 && member.role === TeamMemberRole.OWNER) {
return E.left(TEAM_ONLY_ONE_OWNER);
}
const dtm = await this.conn.query(
`DELETE FROM "TeamMember" WHERE "teamID" = '${teamID}' AND "userUid" = '${userUid}'`,
);
console.log('leaveTeam >>>>>>>>>>', dtm);
this.pubsub.publish(`team/${teamID}/member_removed`, userUid);
return E.right(true);
}
const ownerCount = await this.prisma.teamMember.count({ const ownerCount = await this.prisma.teamMember.count({
where: { where: {
teamID, teamID,
@@ -404,34 +248,6 @@ export class TeamService implements UserDataHandler, OnModuleInit {
const isValidName = this.validateTeamName(name); const isValidName = this.validateTeamName(name);
if (E.isLeft(isValidName)) return isValidName; if (E.isLeft(isValidName)) return isValidName;
if (this.enableRawSql) {
const startQ = Date.now();
const t = await this.conn.query(
`INSERT INTO "Team" (id, name) VALUES ('${new Date().toISOString()}', '${name}') RETURNING *`,
);
const tm = await this.conn.query(
`INSERT INTO "TeamMember" ("id", "userUid", "teamID", "role") VALUES ('${new Date().toISOString()}', '${creatorUid}' , '${
t.rows[0].id
}', '${TeamMemberRole.OWNER}') RETURNING *`,
);
const endQ = Date.now();
``;
console.log(
'createTeam >>>>>>>>>>',
endQ - startQ,
'ms',
'>>>>>',
t.rows[0],
tm.rows[0],
);
return E.right(<Team>{
id: t.rows[0].id,
name: t.rows[0].name,
});
}
const startQ = Date.now();
const team = await this.prisma.team.create({ const team = await this.prisma.team.create({
data: { data: {
name: name, name: name,
@@ -443,42 +259,12 @@ export class TeamService implements UserDataHandler, OnModuleInit {
}, },
}, },
}); });
const endQ = Date.now();
console.log('createTeam >>>>>>>>>> ', endQ - startQ, 'ms', '>>>>>', team);
return E.right(team); return E.right(team);
} }
async getTeamsOfUser(uid: string, cursor: string | null): Promise<Team[]> { async getTeamsOfUser(uid: string, cursor: string | null): Promise<Team[]> {
if (this.enableRawSql) {
const startQ = Date.now();
let users;
if (cursor) {
users = await this.conn.query(
`SELECT * FROM "TeamMember" LEFT JOIN "Team" ON "TeamMember"."teamID" = "Team"."id" WHERE "TeamMember"."userUid" = '${uid}' and "TeamMember"."teamID" > '${cursor}' LIMIT 10`,
);
} else {
users = await this.conn.query(
`SELECT * FROM "TeamMember" LEFT JOIN "Team" ON "TeamMember"."teamID" = "Team"."id" WHERE "TeamMember"."userUid" = '${uid}' LIMIT 10`,
);
}
const endQ = Date.now();
console.log(
'getTeamsOfUser >>>>>>>>>>',
endQ - startQ,
'ms',
'>>>>>',
users.rows,
);
return users.rows.map((entry) => ({
id: entry.teamID,
name: entry.name,
}));
}
if (!cursor) { if (!cursor) {
const startQ = Date.now();
const entries = await this.prisma.teamMember.findMany({ const entries = await this.prisma.teamMember.findMany({
take: 10, take: 10,
where: { where: {
@@ -488,18 +274,9 @@ export class TeamService implements UserDataHandler, OnModuleInit {
team: true, team: true,
}, },
}); });
const endQ = Date.now();
console.log(
'getTeamsOfUser >>>>>>>>>>',
endQ - startQ,
'ms',
'>>>>>',
entries,
);
return entries.map((entry) => entry.team); return entries.map((entry) => entry.team);
} else { } else {
const startQ = Date.now();
const entries = await this.prisma.teamMember.findMany({ const entries = await this.prisma.teamMember.findMany({
take: 10, take: 10,
skip: 1, skip: 1,
@@ -516,56 +293,17 @@ export class TeamService implements UserDataHandler, OnModuleInit {
team: true, team: true,
}, },
}); });
const endQ = Date.now();
console.log(
'getTeamsOfUser >>>>>>>>>>',
endQ - startQ,
'ms',
'>>>>>',
entries,
);
return entries.map((entry) => entry.team); return entries.map((entry) => entry.team);
} }
} }
async getTeamWithID(teamID: string): Promise<Team | null> { async getTeamWithID(teamID: string): Promise<Team | null> {
if (this.enableRawSql) {
const startQ = Date.now();
const team = await this.conn.query(
`SELECT * FROM "Team" WHERE "id" = '${teamID}'`,
);
const endQ = Date.now();
console.log(
'getTeamWithID >>>>>>>>>>',
endQ - startQ,
'ms',
'>>>>>',
team.rows,
);
if (team.rows.length === 0) return null;
return <Team>{
id: team.rows[0].id,
name: team.rows[0].name,
};
}
try { try {
const startQ = Date.now();
const team = await this.prisma.team.findUnique({ const team = await this.prisma.team.findUnique({
where: { where: {
id: teamID, id: teamID,
}, },
}); });
const endQ = Date.now();
console.log(
'getTeamWithID >>>>>>>>>>',
endQ - startQ,
'ms',
'>>>>>',
team,
);
return team; return team;
} catch (_e) { } catch (_e) {
@@ -615,30 +353,7 @@ export class TeamService implements UserDataHandler, OnModuleInit {
teamID: string, teamID: string,
userUid: string, userUid: string,
): Promise<TeamMember | null> { ): Promise<TeamMember | null> {
if (this.enableRawSql) {
const startQ = Date.now();
const member = await this.conn.query(
`SELECT * FROM "TeamMember" WHERE "teamID" = '${teamID}' AND "userUid" = '${userUid}'`,
);
const endQ = Date.now();
console.log(
'getTeamMember >>>>>>>>>>',
endQ - startQ,
'ms',
'>>>>>',
member.rows,
);
if (member.rows.length === 0) return null;
return <TeamMember>{
membershipID: member.rows[0].id,
userUid: member.rows[0].userUid,
role: TeamMemberRole[member.rows[0].role],
};
}
try { try {
const startQ = Date.now();
const teamMember = await this.prisma.teamMember.findUnique({ const teamMember = await this.prisma.teamMember.findUnique({
where: { where: {
teamID_userUid: { teamID_userUid: {
@@ -647,14 +362,6 @@ export class TeamService implements UserDataHandler, OnModuleInit {
}, },
}, },
}); });
const endQ = Date.now();
console.log(
'getTeamMember >>>>>>>>>>',
endQ - startQ,
'ms',
'>>>>>',
teamMember,
);
if (!teamMember) return null; if (!teamMember) return null;
@@ -726,44 +433,11 @@ export class TeamService implements UserDataHandler, OnModuleInit {
} }
async getTeamMembers(teamID: string): Promise<TeamMember[]> { async getTeamMembers(teamID: string): Promise<TeamMember[]> {
if (this.enableRawSql) {
const startQ = Date.now();
const members = await this.conn.query(
`SELECT * FROM "TeamMember" WHERE "teamID" = '${teamID}'`,
);
const endQ = Date.now();
console.log(
'getTeamMembers >>>>>>>>>>',
endQ - startQ,
'ms',
'>>>>>',
members.rows,
);
return members.rows.map((entry) => {
return {
membershipID: entry.id,
userUid: entry.userUid,
role: TeamMemberRole[entry.role],
};
});
}
const startQ = Date.now();
const dbTeamMembers = await this.prisma.teamMember.findMany({ const dbTeamMembers = await this.prisma.teamMember.findMany({
where: { where: {
teamID, teamID,
}, },
}); });
const endQ = Date.now();
console.log(
'getTeamMembers >>>>>>>>>>',
endQ - startQ,
'ms',
'>>>>>',
dbTeamMembers,
);
const members = dbTeamMembers.map( const members = dbTeamMembers.map(
(entry) => (entry) =>
@@ -796,39 +470,8 @@ export class TeamService implements UserDataHandler, OnModuleInit {
teamID: string, teamID: string,
cursor: string | null, cursor: string | null,
): Promise<TeamMember[]> { ): Promise<TeamMember[]> {
if (this.enableRawSql) {
const startQ = Date.now();
let members;
if (cursor) {
members = await this.conn.query(
`SELECT * FROM "TeamMember" WHERE "teamID" = '${teamID}' AND "id" > '${cursor}' LIMIT 10`,
);
}
members = await this.conn.query(
`SELECT * FROM "TeamMember" WHERE "teamID" = '${teamID}' LIMIT 10`,
);
const endQ = Date.now();
console.log(
'getMembersOfTeam >>>>>>>>>>',
endQ - startQ,
'ms',
'>>>>>',
members.rows,
);
return members.rows.map(
(entry) =>
<TeamMember>{
membershipID: entry.id,
userUid: entry.userUid,
role: TeamMemberRole[entry.role],
},
);
}
let teamMembers: DbTeamMember[]; let teamMembers: DbTeamMember[];
const startQ = Date.now();
if (!cursor) { if (!cursor) {
teamMembers = await this.prisma.teamMember.findMany({ teamMembers = await this.prisma.teamMember.findMany({
take: 10, take: 10,
@@ -848,14 +491,6 @@ export class TeamService implements UserDataHandler, OnModuleInit {
}, },
}); });
} }
const endQ = Date.now();
console.log(
'getMembersOfTeam >>>>>>>>>>',
endQ - startQ,
'ms',
'>>>>>',
teamMembers,
);
const members = teamMembers.map( const members = teamMembers.map(
(entry) => (entry) =>

View File

@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
support@hoppscotch.io.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,19 +1,29 @@
<div align="center">
<a href="https://hoppscotch.io">
<img
src="https://avatars.githubusercontent.com/u/56705483"
alt="Hoppscotch Logo"
height="64"
/>
</a>
</div>
<div align="center">
# Hoppscotch CLI <font size=2><sup>ALPHA</sup></font> # Hoppscotch CLI <font size=2><sup>ALPHA</sup></font>
A CLI to run Hoppscotch Test Scripts in CI environments. </div>
A CLI to run Hoppscotch test scripts in CI environments.
### **Commands:** ### **Commands:**
- `hopp test [options] [file]`: testing hoppscotch collection.json file - `hopp test [options] [file]`: testing hoppscotch collection.json file
### **Usage:** ### **Usage:**
```
```bash
hopp [options or commands] arguments hopp [options or commands] arguments
``` ```
### **Options:** ### **Options:**
- `-v`, `--ver`: see the current version of the CLI - `-v`, `--ver`: see the current version of the CLI
- `-h`, `--help`: display help for command - `-h`, `--help`: display help for command
@@ -35,21 +45,17 @@ hopp [options or commands] arguments
- Executes and outputs test-script response. - Executes and outputs test-script response.
#### Options: #### Options:
##### `-e <file_path>` / `--env <file_path>` ##### `-e <file_path>` / `--env <file_path>`
- Accepts path to env.json with contents in below format: - Accepts path to env.json with contents in below format:
```json ```json
{ {
"ENV1":"value1", "ENV1":"value1",
"ENV2":"value2" "ENV2":"value2"
} }
``` ```
- You can now access those variables using `pw.env.get('<var_name>')` - You can now access those variables using `pw.env.get('<var_name>')`
Taking the above example, `pw.env.get("ENV1")` will return `"value1"` Taking the above example, `pw.env.get("ENV1")` will return `"value1"`
## Install ## Install
@@ -69,59 +75,4 @@ npm i -g @hoppscotch/cli
## **Contributing:** ## **Contributing:**
When contributing to this repository, please first discuss the change you wish to make via issue, To get started contributing to the repository, please read **[CONTRIBUTING.md](./CONTRIBUTING.md)**
email, or any other method with the owners of this repository before making a change.
Please note we have a code of conduct, please follow it in all your interactions with the project.
## Pull Request Process
1. Ensure any install or build dependencies are removed before the end of the layer when doing a
build.
2. Update the README.md with details of changes to the interface, this includes new environment
variables, exposed ports, useful file locations and container parameters.
3. Increase the version numbers in any examples files and the README.md to the new version that this
Pull Request would represent. The versioning scheme we use is [SemVer](https://semver.org).
4. You may merge the Pull Request once you have the sign-off of two other developers, or if you
do not have permission to do that, you may request the second reviewer merge it for you.
## Set Up The Development Environment
1. After cloning the repository, execute the following commands:
```bash
pnpm install
pnpm run build
```
2. In order to test locally, you can use two types of package linking:
1. The 'pnpm exec' way (preferred since it does not hamper your original installation of the CLI):
```bash
pnpm link @hoppscotch/cli
// Then to use or test the CLI:
pnpm exec hopp
// After testing, to remove the package linking:
pnpm rm @hoppscotch/cli
```
2. The 'global' way (warning: this might override the globally installed CLI, if exists):
```bash
sudo pnpm link --global
// Then to use or test the CLI:
hopp
// After testing, to remove the package linking:
sudo pnpm rm --global @hoppscotch/cli
```
3. To use the Typescript watch scripts:
```bash
pnpm run dev
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 926 KiB

After

Width:  |  Height:  |  Size: 666 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 510 KiB

After

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 535 KiB

After

Width:  |  Height:  |  Size: 382 KiB

View File

@@ -56,7 +56,7 @@ export const bindings: {
"alt-x": "request.method.delete", "alt-x": "request.method.delete",
"ctrl-k": "modals.search.toggle", "ctrl-k": "modals.search.toggle",
"ctrl-/": "flyouts.keybinds.toggle", "ctrl-/": "flyouts.keybinds.toggle",
"shift-/": "modals.support.toggle", "?": "modals.support.toggle",
"ctrl-m": "modals.share.toggle", "ctrl-m": "modals.share.toggle",
"alt-r": "navigation.jump.rest", "alt-r": "navigation.jump.rest",
"alt-q": "navigation.jump.graphql", "alt-q": "navigation.jump.graphql",
@@ -120,8 +120,7 @@ function generateKeybindingString(ev: KeyboardEvent): ShortcutKey | null {
} }
function getPressedKey(ev: KeyboardEvent): Key | null { function getPressedKey(ev: KeyboardEvent): Key | null {
const val = ev.code.toLowerCase() const val = ev.key.toLowerCase()
// Check arrow keys // Check arrow keys
if (val === "arrowup") return "up" if (val === "arrowup") return "up"
else if (val === "arrowdown") return "down" else if (val === "arrowdown") return "down"
@@ -129,20 +128,21 @@ function getPressedKey(ev: KeyboardEvent): Key | null {
else if (val === "arrowright") return "right" else if (val === "arrowright") return "right"
// Check letter keys // Check letter keys
const isLetter = val.startsWith("key") const isLetter = ev.code.toLowerCase().startsWith("key")
if (isLetter) return val.substring(3) as Key if (isLetter) return ev.code.toLowerCase().substring(3) as Key
// Check if number keys // Check if number keys
const isDigit = val.startsWith("digit") if (val.length === 1 && !isNaN(val as any)) return val as Key
if (isDigit) return val.substring(5) as Key
// Check if slash // Check if question mark
if (val === "slash") return "/" if (val === "?") return "?"
// Check if question mark
if (val === "/") return "/"
// Check if period // Check if period
if (val === "period") return "." if (val === ".") return "."
// Check if enter
if (val === "enter") return "enter" if (val === "enter") return "enter"
// If no other cases match, this is not a valid key // If no other cases match, this is not a valid key

167
pnpm-lock.yaml generated
View File

@@ -184,9 +184,6 @@ importers:
passport-microsoft: passport-microsoft:
specifier: ^1.0.0 specifier: ^1.0.0
version: 1.0.0 version: 1.0.0
pg:
specifier: ^8.11.3
version: 8.11.3
prisma: prisma:
specifier: ^4.16.2 specifier: ^4.16.2
version: 4.16.2 version: 4.16.2
@@ -1342,7 +1339,7 @@ importers:
version: 0.13.1(vite@3.2.4)(workbox-build@6.6.0)(workbox-window@6.6.0) version: 0.13.1(vite@3.2.4)(workbox-build@6.6.0)(workbox-window@6.6.0)
vite-plugin-vue-layouts: vite-plugin-vue-layouts:
specifier: ^0.7.0 specifier: ^0.7.0
version: 0.7.0(vite@3.2.4)(vue-router@4.2.4)(vue@3.2.45) version: 0.7.0(vite@3.2.4)(vue-router@4.1.0)(vue@3.2.45)
vite-plugin-windicss: vite-plugin-windicss:
specifier: ^1.8.8 specifier: ^1.8.8
version: 1.8.8(vite@3.2.4) version: 1.8.8(vite@3.2.4)
@@ -6065,8 +6062,8 @@ packages:
vue-i18n: vue-i18n:
optional: true optional: true
dependencies: dependencies:
'@intlify/message-compiler': 9.3.0 '@intlify/message-compiler': 9.3.0-beta.26
'@intlify/shared': 9.3.0 '@intlify/shared': 9.3.0-beta.26
jsonc-eslint-parser: 1.4.1 jsonc-eslint-parser: 1.4.1
source-map: 0.6.1 source-map: 0.6.1
vue-i18n: 9.2.2(vue@3.3.4) vue-i18n: 9.2.2(vue@3.3.4)
@@ -6119,14 +6116,6 @@ packages:
'@intlify/shared': 9.2.2 '@intlify/shared': 9.2.2
source-map: 0.6.1 source-map: 0.6.1
/@intlify/message-compiler@9.3.0:
resolution: {integrity: sha512-D8tSJEhTCSFcCzkThjE4Sbk1tIdvzkYa1FaVIzUtZ8hKPATvokNrOiDw1i/h671m8A80l9Ywq594i/LPTB6EJA==}
engines: {node: '>= 16'}
dependencies:
'@intlify/shared': 9.3.0
source-map-js: 1.0.2
dev: true
/@intlify/message-compiler@9.3.0-beta.20: /@intlify/message-compiler@9.3.0-beta.20:
resolution: {integrity: sha512-hwqQXyTnDzAVZ300SU31jO0+3OJbpOdfVU6iBkrmNpS7t2HRnVACo0EwcEXzJa++4EVDreqz5OeqJbt+PeSGGA==} resolution: {integrity: sha512-hwqQXyTnDzAVZ300SU31jO0+3OJbpOdfVU6iBkrmNpS7t2HRnVACo0EwcEXzJa++4EVDreqz5OeqJbt+PeSGGA==}
engines: {node: '>= 16'} engines: {node: '>= 16'}
@@ -6135,17 +6124,25 @@ packages:
source-map-js: 1.0.2 source-map-js: 1.0.2
dev: true dev: true
/@intlify/message-compiler@9.3.0-beta.26:
resolution: {integrity: sha512-qsfU6Lca7mI80ts1vgy+pfNvGm2gHy0nERpT/K1GYgnECmsKwud0e8SG1PPxKPEHKa5Mdngzs4pS7X1wH0SCGA==}
engines: {node: '>= 16'}
dependencies:
'@intlify/shared': 9.3.0-beta.26
source-map-js: 1.0.2
dev: true
/@intlify/shared@9.2.2: /@intlify/shared@9.2.2:
resolution: {integrity: sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==} resolution: {integrity: sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==}
engines: {node: '>= 14'} engines: {node: '>= 14'}
/@intlify/shared@9.3.0: /@intlify/shared@9.3.0-beta.20:
resolution: {integrity: sha512-MMGRz6zWxtz7rHtxIIdnyb8SYOIaaseN1IvUhAEs9tOW4u77RD4DFp4qgPXesp2Gxo/5QitH9kwSs0jnxGUNEw==} resolution: {integrity: sha512-RucSPqh8O9FFxlYUysQTerSw0b9HIRpyoN1Zjogpm0qLiHK+lBNSa5sh1nCJ4wSsNcjphzgpLQCyR60GZlRV8g==}
engines: {node: '>= 16'} engines: {node: '>= 16'}
dev: true dev: true
/@intlify/shared@9.3.0-beta.20: /@intlify/shared@9.3.0-beta.26:
resolution: {integrity: sha512-RucSPqh8O9FFxlYUysQTerSw0b9HIRpyoN1Zjogpm0qLiHK+lBNSa5sh1nCJ4wSsNcjphzgpLQCyR60GZlRV8g==} resolution: {integrity: sha512-RpCtfSYIg4tSskrazTr5+WCHyw6qpgwdIxC+x3nCnrPGxyk+en9FoSbadVfx/w7uDTdyhKslEw4d2+qhNc0s4Q==}
engines: {node: '>= 16'} engines: {node: '>= 16'}
dev: true dev: true
@@ -6165,7 +6162,7 @@ packages:
optional: true optional: true
dependencies: dependencies:
'@intlify/bundle-utils': 7.0.0 '@intlify/bundle-utils': 7.0.0
'@intlify/shared': 9.3.0 '@intlify/shared': 9.3.0-beta.26
'@rollup/pluginutils': 4.2.1 '@rollup/pluginutils': 4.2.1
debug: 4.3.4(supports-color@9.2.2) debug: 4.3.4(supports-color@9.2.2)
fast-glob: 3.2.11 fast-glob: 3.2.11
@@ -6192,7 +6189,7 @@ packages:
optional: true optional: true
dependencies: dependencies:
'@intlify/bundle-utils': 3.4.0(vue-i18n@9.2.2) '@intlify/bundle-utils': 3.4.0(vue-i18n@9.2.2)
'@intlify/shared': 9.3.0 '@intlify/shared': 9.3.0-beta.26
'@rollup/pluginutils': 4.2.1 '@rollup/pluginutils': 4.2.1
debug: 4.3.4(supports-color@9.2.2) debug: 4.3.4(supports-color@9.2.2)
fast-glob: 3.3.1 fast-glob: 3.3.1
@@ -6220,7 +6217,7 @@ packages:
optional: true optional: true
dependencies: dependencies:
'@intlify/bundle-utils': 3.4.0(vue-i18n@9.2.2) '@intlify/bundle-utils': 3.4.0(vue-i18n@9.2.2)
'@intlify/shared': 9.3.0 '@intlify/shared': 9.3.0-beta.26
'@rollup/pluginutils': 4.2.1 '@rollup/pluginutils': 4.2.1
debug: 4.3.4(supports-color@9.2.2) debug: 4.3.4(supports-color@9.2.2)
fast-glob: 3.3.1 fast-glob: 3.3.1
@@ -6247,7 +6244,7 @@ packages:
optional: true optional: true
dependencies: dependencies:
'@intlify/bundle-utils': 3.4.0(vue-i18n@9.2.2) '@intlify/bundle-utils': 3.4.0(vue-i18n@9.2.2)
'@intlify/shared': 9.3.0 '@intlify/shared': 9.3.0-beta.26
'@rollup/pluginutils': 4.2.1 '@rollup/pluginutils': 4.2.1
debug: 4.3.4(supports-color@9.2.2) debug: 4.3.4(supports-color@9.2.2)
fast-glob: 3.3.1 fast-glob: 3.3.1
@@ -11141,11 +11138,6 @@ packages:
/buffer-from@1.1.2: /buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
/buffer-writer@2.0.0:
resolution: {integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==}
engines: {node: '>=4'}
dev: false
/buffer@5.7.1: /buffer@5.7.1:
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
dependencies: dependencies:
@@ -18945,10 +18937,6 @@ packages:
netmask: 2.0.2 netmask: 2.0.2
dev: false dev: false
/packet-reader@1.0.0:
resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==}
dev: false
/paho-mqtt@1.1.0: /paho-mqtt@1.1.0:
resolution: {integrity: sha512-KPbL9KAB0ASvhSDbOrZBaccXS+/s7/LIofbPyERww8hM5Ko71GUJQ6Nmg0BWqj8phAIT8zdf/Sd/RftHU9i2HA==} resolution: {integrity: sha512-KPbL9KAB0ASvhSDbOrZBaccXS+/s7/LIofbPyERww8hM5Ko71GUJQ6Nmg0BWqj8phAIT8zdf/Sd/RftHU9i2HA==}
dev: false dev: false
@@ -19230,70 +19218,6 @@ packages:
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
dev: false dev: false
/pg-cloudflare@1.1.1:
resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==}
requiresBuild: true
dev: false
optional: true
/pg-connection-string@2.6.2:
resolution: {integrity: sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==}
dev: false
/pg-int8@1.0.1:
resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
engines: {node: '>=4.0.0'}
dev: false
/pg-pool@3.6.1(pg@8.11.3):
resolution: {integrity: sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==}
peerDependencies:
pg: '>=8.0'
dependencies:
pg: 8.11.3
dev: false
/pg-protocol@1.6.0:
resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==}
dev: false
/pg-types@2.2.0:
resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==}
engines: {node: '>=4'}
dependencies:
pg-int8: 1.0.1
postgres-array: 2.0.0
postgres-bytea: 1.0.0
postgres-date: 1.0.7
postgres-interval: 1.2.0
dev: false
/pg@8.11.3:
resolution: {integrity: sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==}
engines: {node: '>= 8.0.0'}
peerDependencies:
pg-native: '>=3.0.1'
peerDependenciesMeta:
pg-native:
optional: true
dependencies:
buffer-writer: 2.0.0
packet-reader: 1.0.0
pg-connection-string: 2.6.2
pg-pool: 3.6.1(pg@8.11.3)
pg-protocol: 1.6.0
pg-types: 2.2.0
pgpass: 1.0.5
optionalDependencies:
pg-cloudflare: 1.1.1
dev: false
/pgpass@1.0.5:
resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==}
dependencies:
split2: 4.2.0
dev: false
/pick-util@1.1.5: /pick-util@1.1.5:
resolution: {integrity: sha512-H0MaM8T7wpQ/azvB12ChZw7kpSFzjsgv3Z+N7fUWnL1McTGSEeroCngcK4eOPiFQq08rAyKX3hadcAB1kUqfXA==} resolution: {integrity: sha512-H0MaM8T7wpQ/azvB12ChZw7kpSFzjsgv3Z+N7fUWnL1McTGSEeroCngcK4eOPiFQq08rAyKX3hadcAB1kUqfXA==}
dependencies: dependencies:
@@ -19403,28 +19327,6 @@ packages:
picocolors: 1.0.0 picocolors: 1.0.0
source-map-js: 1.0.2 source-map-js: 1.0.2
/postgres-array@2.0.0:
resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
engines: {node: '>=4'}
dev: false
/postgres-bytea@1.0.0:
resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==}
engines: {node: '>=0.10.0'}
dev: false
/postgres-date@1.0.7:
resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==}
engines: {node: '>=0.10.0'}
dev: false
/postgres-interval@1.2.0:
resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
engines: {node: '>=0.10.0'}
dependencies:
xtend: 4.0.2
dev: false
/postman-collection@4.2.0: /postman-collection@4.2.0:
resolution: {integrity: sha512-tvOLgN1h6Kab6dt43PmBoV5kYO/YUta3x0C2QqfmbzmHZe47VTpZ/+gIkGlbNhjKNPUUub5X6ehxYKoaTYdy1w==} resolution: {integrity: sha512-tvOLgN1h6Kab6dt43PmBoV5kYO/YUta3x0C2QqfmbzmHZe47VTpZ/+gIkGlbNhjKNPUUub5X6ehxYKoaTYdy1w==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -20839,11 +20741,6 @@ packages:
readable-stream: 3.6.0 readable-stream: 3.6.0
dev: true dev: true
/split2@4.2.0:
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
engines: {node: '>= 10.x'}
dev: false
/split@1.0.1: /split@1.0.1:
resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==}
dependencies: dependencies:
@@ -23174,23 +23071,6 @@ packages:
- supports-color - supports-color
dev: true dev: true
/vite-plugin-vue-layouts@0.7.0(vite@3.2.4)(vue-router@4.2.4)(vue@3.2.45):
resolution: {integrity: sha512-k5XDmRNFo4M/GmUjhbRXj2WmJiFcGoVI8l/uZ72RHyRDQr4wE/6Zq/KFq0lqXomWQxTSzakQRUswzNwtvZLE8A==}
peerDependencies:
vite: ^2.5.0 || ^3.0.0-0
vue: ^2.6.12 || ^3.2.4
vue-router: ^3.5.1 || ^ 4.0.11
dependencies:
'@vue/compiler-sfc': 3.3.4
debug: 4.3.4(supports-color@9.2.2)
fast-glob: 3.3.1
vite: 3.2.4(@types/node@17.0.45)(sass@1.53.0)(terser@5.19.2)
vue: 3.2.45
vue-router: 4.2.4(vue@3.2.45)
transitivePeerDependencies:
- supports-color
dev: true
/vite-plugin-vue-layouts@0.8.0(vite@4.4.9)(vue-router@4.2.4)(vue@3.3.4): /vite-plugin-vue-layouts@0.8.0(vite@4.4.9)(vue-router@4.2.4)(vue@3.3.4):
resolution: {integrity: sha512-UZW2nSV2LraTSe7gsAL46hfdi7a0X1RvkGGoJVtA2O8beu7anzpXFwQLou8+kHy31CzVycT4gIPySBsHhtBN5g==} resolution: {integrity: sha512-UZW2nSV2LraTSe7gsAL46hfdi7a0X1RvkGGoJVtA2O8beu7anzpXFwQLou8+kHy31CzVycT4gIPySBsHhtBN5g==}
peerDependencies: peerDependencies:
@@ -23894,15 +23774,6 @@ packages:
'@vue/devtools-api': 6.5.0 '@vue/devtools-api': 6.5.0
vue: 3.2.45 vue: 3.2.45
/vue-router@4.2.4(vue@3.2.45):
resolution: {integrity: sha512-9PISkmaCO02OzPVOMq2w82ilty6+xJmQrarYZDkjZBfl4RvYAlt4PKnEX21oW4KTtWfa9OuO/b3qk1Od3AEdCQ==}
peerDependencies:
vue: ^3.2.0
dependencies:
'@vue/devtools-api': 6.5.0
vue: 3.2.45
dev: true
/vue-router@4.2.4(vue@3.3.4): /vue-router@4.2.4(vue@3.3.4):
resolution: {integrity: sha512-9PISkmaCO02OzPVOMq2w82ilty6+xJmQrarYZDkjZBfl4RvYAlt4PKnEX21oW4KTtWfa9OuO/b3qk1Od3AEdCQ==} resolution: {integrity: sha512-9PISkmaCO02OzPVOMq2w82ilty6+xJmQrarYZDkjZBfl4RvYAlt4PKnEX21oW4KTtWfa9OuO/b3qk1Od3AEdCQ==}
peerDependencies: peerDependencies:

View File

@@ -18,11 +18,11 @@ RUN pnpm exec prisma generate
RUN pnpm run build RUN pnpm run build
# Remove the env file to avoid backend copying it in and using it # Remove the env file to avoid backend copying it in and using it
RUN rm "../../.env" RUN rm "../../.env"
ENV PRODUCTION="false" ENV PRODUCTION="true"
ENV PORT=3170 ENV PORT=3170
ENV APP_PORT=${PORT} ENV APP_PORT=${PORT}
ENV DB_URL=${DATABASE_URL} ENV DB_URL=${DATABASE_URL}
CMD ["pnpm", "run", "start:dev"] CMD ["pnpm", "run", "start:prod"]
EXPOSE 3170 EXPOSE 3170
FROM base_builder as fe_builder FROM base_builder as fe_builder