diff --git a/.github/workflows/client-build.yml b/.github/workflows/client-build.yml
new file mode 100644
index 0000000..e3ab06f
--- /dev/null
+++ b/.github/workflows/client-build.yml
@@ -0,0 +1,34 @@
+name: Build Client
+on:
+ push:
+ branches: [master]
+ paths:
+ - app/client/**
+ pull_request:
+ paths:
+ - app/client/**
+
+jobs:
+ client-build:
+ name: "Build Client"
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ sparse-checkout: "app/client"
+ sparse-checkout-cone-mode: false
+
+ - name: Install Node.js, NPM and Yarn
+ uses: actions/setup-node@v1
+ with:
+ node-version: 20.x
+
+ - name: Install dependencies
+ working-directory: ./app/client
+ run: yarn install --frozen-lockfile
+
+ - name: Build
+ working-directory: ./app/client
+ run: yarn run build
diff --git a/.github/workflows/prettier-check.yml b/.github/workflows/prettier-check.yml
new file mode 100644
index 0000000..715aa7f
--- /dev/null
+++ b/.github/workflows/prettier-check.yml
@@ -0,0 +1,29 @@
+name: Prettier Check
+on:
+ push:
+ branches: [master]
+ paths:
+ - app/server/**
+ - app/client/**
+ pull_request:
+ paths:
+ - app/server/**
+ - app/client/**
+
+jobs:
+ prettier:
+ name: "Checking code style"
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Install Node.js, NPM and Yarn
+ uses: actions/setup-node@v1
+ with:
+ node-version: 20.x
+
+ - name: Install dependencies
+ run: npm install
+
+ - name: Run Prettier check
+ run: npm run prettier:check
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000..98a2059
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,115 @@
+name: Publish
+on:
+ push:
+ tags:
+ - "*"
+
+env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+jobs:
+ client-build:
+ name: "Build Client"
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ sparse-checkout: "app/client"
+ sparse-checkout-cone-mode: false
+
+ - name: Install Node.js, NPM and Yarn
+ uses: actions/setup-node@v1
+ with:
+ node-version: 20.x
+
+ - name: Set Versions
+ uses: actions/github-script@v4
+ id: set_version
+ with:
+ script: |
+ const tag = context.ref.substring(10).replace('v', '')
+ core.setOutput('tag', tag)
+ core.setOutput('version', tag.split("-")[0])
+
+ - name: Add version to package.json
+ uses: jaywcjlove/github-action-package@main
+ with:
+ path: "./app/client/package.json"
+ data: |
+ {
+ "version": "${{ steps.set_version.outputs.tag }}"
+ }
+
+ - name: Install dependencies
+ working-directory: ./app/client
+ run: yarn install --frozen-lockfile
+
+ - name: Build
+ working-directory: ./app/client
+ run: yarn run build
+
+ - name: "Upload Artifact"
+ uses: actions/upload-artifact@v3
+ with:
+ name: client-build
+ path: "./app/client/build"
+
+ server-build:
+ name: "Build Server"
+
+ needs: [client-build]
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ os: [darwin, linux]
+
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ sparse-checkout: "app/server"
+ sparse-checkout-cone-mode: false
+
+ - uses: denoland/setup-deno@v1
+ with:
+ deno-version: vx.x.x
+
+ - name: Set Versions
+ uses: actions/github-script@v4
+ id: set_version
+ with:
+ script: |
+ const tag = context.ref.substring(10).replace('v', '')
+ core.setOutput('tag', tag)
+ core.setOutput('version', tag.split("-")[0])
+
+ - name: Replace consts.ts version
+ uses: richardrigutins/replace-in-files@v2
+ with:
+ files: "./app/server/mod.ts"
+ search-text: "__VERSION__"
+ replacement-text: ${{ steps.set_version.outputs.tag }}
+
+ - name: Download client-build artifact
+ uses: actions/download-artifact@v3
+ with:
+ name: client-build
+ path: ./app/server
+
+ - name: Build index
+ working-directory: ./app/server
+ run: deno task build:index
+
+ - name: Compile
+ working-directory: ./app/server
+ run: deno task compile:${{ matrix.os }}
+
+ - name: "Upload to release"
+ uses: svenstaro/upload-release-action@v2
+ with:
+ repo_token: ${{ secrets.GITHUB_TOKEN }}
+ file: ./build/assets_editor_${{ matrix.os }}
+ tag: ${{ github.ref }}
+ overwrite: true
+ file_glob: true
diff --git a/.github/workflows/server-build.yml b/.github/workflows/server-build.yml
new file mode 100644
index 0000000..1e38eba
--- /dev/null
+++ b/.github/workflows/server-build.yml
@@ -0,0 +1,29 @@
+name: Build Auth
+on:
+ push:
+ branches: [master]
+ paths:
+ - app/server/**
+ pull_request:
+ paths:
+ - app/server/**
+
+jobs:
+ auth-build:
+ name: "Build Server"
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ sparse-checkout: "app/server"
+ sparse-checkout-cone-mode: false
+
+ - uses: denoland/setup-deno@v1
+ with:
+ deno-version: vx.x.x
+
+ - name: Build
+ working-directory: ./app/server
+ run: deno task build
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bb5c8c1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/node_modules
+.idea
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..cbe5ad1
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,437 @@
+Attribution-NonCommercial-ShareAlike 4.0 International
+
+=======================================================================
+
+Creative Commons Corporation ("Creative Commons") is not a law firm and
+does not provide legal services or legal advice. Distribution of
+Creative Commons public licenses does not create a lawyer-client or
+other relationship. Creative Commons makes its licenses and related
+information available on an "as-is" basis. Creative Commons gives no
+warranties regarding its licenses, any material licensed under their
+terms and conditions, or any related information. Creative Commons
+disclaims all liability for damages resulting from their use to the
+fullest extent possible.
+
+Using Creative Commons Public Licenses
+
+Creative Commons public licenses provide a standard set of terms and
+conditions that creators and other rights holders may use to share
+original works of authorship and other material subject to copyright
+and certain other rights specified in the public license below. The
+following considerations are for informational purposes only, are not
+exhaustive, and do not form part of our licenses.
+
+ Considerations for licensors: Our public licenses are
+ intended for use by those authorized to give the public
+ permission to use material in ways otherwise restricted by
+ copyright and certain other rights. Our licenses are
+ irrevocable. Licensors should read and understand the terms
+ and conditions of the license they choose before applying it.
+ Licensors should also secure all rights necessary before
+ applying our licenses so that the public can reuse the
+ material as expected. Licensors should clearly mark any
+ material not subject to the license. This includes other CC-
+ licensed material, or material used under an exception or
+ limitation to copyright. More considerations for licensors:
+ wiki.creativecommons.org/Considerations_for_licensors
+
+ Considerations for the public: By using one of our public
+ licenses, a licensor grants the public permission to use the
+ licensed material under specified terms and conditions. If
+ the licensor's permission is not necessary for any reason--for
+ example, because of any applicable exception or limitation to
+ copyright--then that use is not regulated by the license. Our
+ licenses grant only permissions under copyright and certain
+ other rights that a licensor has authority to grant. Use of
+ the licensed material may still be restricted for other
+ reasons, including because others have copyright or other
+ rights in the material. A licensor may make special requests,
+ such as asking that all changes be marked or described.
+ Although not required by our licenses, you are encouraged to
+ respect those requests where reasonable. More considerations
+ for the public:
+ wiki.creativecommons.org/Considerations_for_licensees
+
+=======================================================================
+
+Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
+Public License
+
+By exercising the Licensed Rights (defined below), You accept and agree
+to be bound by the terms and conditions of this Creative Commons
+Attribution-NonCommercial-ShareAlike 4.0 International Public License
+("Public License"). To the extent this Public License may be
+interpreted as a contract, You are granted the Licensed Rights in
+consideration of Your acceptance of these terms and conditions, and the
+Licensor grants You such rights in consideration of benefits the
+Licensor receives from making the Licensed Material available under
+these terms and conditions.
+
+
+Section 1 -- Definitions.
+
+ a. Adapted Material means material subject to Copyright and Similar
+ Rights that is derived from or based upon the Licensed Material
+ and in which the Licensed Material is translated, altered,
+ arranged, transformed, or otherwise modified in a manner requiring
+ permission under the Copyright and Similar Rights held by the
+ Licensor. For purposes of this Public License, where the Licensed
+ Material is a musical work, performance, or sound recording,
+ Adapted Material is always produced where the Licensed Material is
+ synched in timed relation with a moving image.
+
+ b. Adapter's License means the license You apply to Your Copyright
+ and Similar Rights in Your contributions to Adapted Material in
+ accordance with the terms and conditions of this Public License.
+
+ c. BY-NC-SA Compatible License means a license listed at
+ creativecommons.org/compatiblelicenses, approved by Creative
+ Commons as essentially the equivalent of this Public License.
+
+ d. Copyright and Similar Rights means copyright and/or similar rights
+ closely related to copyright including, without limitation,
+ performance, broadcast, sound recording, and Sui Generis Database
+ Rights, without regard to how the rights are labeled or
+ categorized. For purposes of this Public License, the rights
+ specified in Section 2(b)(1)-(2) are not Copyright and Similar
+ Rights.
+
+ e. Effective Technological Measures means those measures that, in the
+ absence of proper authority, may not be circumvented under laws
+ fulfilling obligations under Article 11 of the WIPO Copyright
+ Treaty adopted on December 20, 1996, and/or similar international
+ agreements.
+
+ f. Exceptions and Limitations means fair use, fair dealing, and/or
+ any other exception or limitation to Copyright and Similar Rights
+ that applies to Your use of the Licensed Material.
+
+ g. License Elements means the license attributes listed in the name
+ of a Creative Commons Public License. The License Elements of this
+ Public License are Attribution, NonCommercial, and ShareAlike.
+
+ h. Licensed Material means the artistic or literary work, database,
+ or other material to which the Licensor applied this Public
+ License.
+
+ i. Licensed Rights means the rights granted to You subject to the
+ terms and conditions of this Public License, which are limited to
+ all Copyright and Similar Rights that apply to Your use of the
+ Licensed Material and that the Licensor has authority to license.
+
+ j. Licensor means the individual(s) or entity(ies) granting rights
+ under this Public License.
+
+ k. NonCommercial means not primarily intended for or directed towards
+ commercial advantage or monetary compensation. For purposes of
+ this Public License, the exchange of the Licensed Material for
+ other material subject to Copyright and Similar Rights by digital
+ file-sharing or similar means is NonCommercial provided there is
+ no payment of monetary compensation in connection with the
+ exchange.
+
+ l. Share means to provide material to the public by any means or
+ process that requires permission under the Licensed Rights, such
+ as reproduction, public display, public performance, distribution,
+ dissemination, communication, or importation, and to make material
+ available to the public including in ways that members of the
+ public may access the material from a place and at a time
+ individually chosen by them.
+
+ m. Sui Generis Database Rights means rights other than copyright
+ resulting from Directive 96/9/EC of the European Parliament and of
+ the Council of 11 March 1996 on the legal protection of databases,
+ as amended and/or succeeded, as well as other essentially
+ equivalent rights anywhere in the world.
+
+ n. You means the individual or entity exercising the Licensed Rights
+ under this Public License. Your has a corresponding meaning.
+
+
+Section 2 -- Scope.
+
+ a. License grant.
+
+ 1. Subject to the terms and conditions of this Public License,
+ the Licensor hereby grants You a worldwide, royalty-free,
+ non-sublicensable, non-exclusive, irrevocable license to
+ exercise the Licensed Rights in the Licensed Material to:
+
+ a. reproduce and Share the Licensed Material, in whole or
+ in part, for NonCommercial purposes only; and
+
+ b. produce, reproduce, and Share Adapted Material for
+ NonCommercial purposes only.
+
+ 2. Exceptions and Limitations. For the avoidance of doubt, where
+ Exceptions and Limitations apply to Your use, this Public
+ License does not apply, and You do not need to comply with
+ its terms and conditions.
+
+ 3. Term. The term of this Public License is specified in Section
+ 6(a).
+
+ 4. Media and formats; technical modifications allowed. The
+ Licensor authorizes You to exercise the Licensed Rights in
+ all media and formats whether now known or hereafter created,
+ and to make technical modifications necessary to do so. The
+ Licensor waives and/or agrees not to assert any right or
+ authority to forbid You from making technical modifications
+ necessary to exercise the Licensed Rights, including
+ technical modifications necessary to circumvent Effective
+ Technological Measures. For purposes of this Public License,
+ simply making modifications authorized by this Section 2(a)
+ (4) never produces Adapted Material.
+
+ 5. Downstream recipients.
+
+ a. Offer from the Licensor -- Licensed Material. Every
+ recipient of the Licensed Material automatically
+ receives an offer from the Licensor to exercise the
+ Licensed Rights under the terms and conditions of this
+ Public License.
+
+ b. Additional offer from the Licensor -- Adapted Material.
+ Every recipient of Adapted Material from You
+ automatically receives an offer from the Licensor to
+ exercise the Licensed Rights in the Adapted Material
+ under the conditions of the Adapter's License You apply.
+
+ c. No downstream restrictions. You may not offer or impose
+ any additional or different terms or conditions on, or
+ apply any Effective Technological Measures to, the
+ Licensed Material if doing so restricts exercise of the
+ Licensed Rights by any recipient of the Licensed
+ Material.
+
+ 6. No endorsement. Nothing in this Public License constitutes or
+ may be construed as permission to assert or imply that You
+ are, or that Your use of the Licensed Material is, connected
+ with, or sponsored, endorsed, or granted official status by,
+ the Licensor or others designated to receive attribution as
+ provided in Section 3(a)(1)(A)(i).
+
+ b. Other rights.
+
+ 1. Moral rights, such as the right of integrity, are not
+ licensed under this Public License, nor are publicity,
+ privacy, and/or other similar personality rights; however, to
+ the extent possible, the Licensor waives and/or agrees not to
+ assert any such rights held by the Licensor to the limited
+ extent necessary to allow You to exercise the Licensed
+ Rights, but not otherwise.
+
+ 2. Patent and trademark rights are not licensed under this
+ Public License.
+
+ 3. To the extent possible, the Licensor waives any right to
+ collect royalties from You for the exercise of the Licensed
+ Rights, whether directly or through a collecting society
+ under any voluntary or waivable statutory or compulsory
+ licensing scheme. In all other cases the Licensor expressly
+ reserves any right to collect such royalties, including when
+ the Licensed Material is used other than for NonCommercial
+ purposes.
+
+
+Section 3 -- License Conditions.
+
+Your exercise of the Licensed Rights is expressly made subject to the
+following conditions.
+
+ a. Attribution.
+
+ 1. If You Share the Licensed Material (including in modified
+ form), You must:
+
+ a. retain the following if it is supplied by the Licensor
+ with the Licensed Material:
+
+ i. identification of the creator(s) of the Licensed
+ Material and any others designated to receive
+ attribution, in any reasonable manner requested by
+ the Licensor (including by pseudonym if
+ designated);
+
+ ii. a copyright notice;
+
+ iii. a notice that refers to this Public License;
+
+ iv. a notice that refers to the disclaimer of
+ warranties;
+
+ v. a URI or hyperlink to the Licensed Material to the
+ extent reasonably practicable;
+
+ b. indicate if You modified the Licensed Material and
+ retain an indication of any previous modifications; and
+
+ c. indicate the Licensed Material is licensed under this
+ Public License, and include the text of, or the URI or
+ hyperlink to, this Public License.
+
+ 2. You may satisfy the conditions in Section 3(a)(1) in any
+ reasonable manner based on the medium, means, and context in
+ which You Share the Licensed Material. For example, it may be
+ reasonable to satisfy the conditions by providing a URI or
+ hyperlink to a resource that includes the required
+ information.
+ 3. If requested by the Licensor, You must remove any of the
+ information required by Section 3(a)(1)(A) to the extent
+ reasonably practicable.
+
+ b. ShareAlike.
+
+ In addition to the conditions in Section 3(a), if You Share
+ Adapted Material You produce, the following conditions also apply.
+
+ 1. The Adapter's License You apply must be a Creative Commons
+ license with the same License Elements, this version or
+ later, or a BY-NC-SA Compatible License.
+
+ 2. You must include the text of, or the URI or hyperlink to, the
+ Adapter's License You apply. You may satisfy this condition
+ in any reasonable manner based on the medium, means, and
+ context in which You Share Adapted Material.
+
+ 3. You may not offer or impose any additional or different terms
+ or conditions on, or apply any Effective Technological
+ Measures to, Adapted Material that restrict exercise of the
+ rights granted under the Adapter's License You apply.
+
+
+Section 4 -- Sui Generis Database Rights.
+
+Where the Licensed Rights include Sui Generis Database Rights that
+apply to Your use of the Licensed Material:
+
+ a. for the avoidance of doubt, Section 2(a)(1) grants You the right
+ to extract, reuse, reproduce, and Share all or a substantial
+ portion of the contents of the database for NonCommercial purposes
+ only;
+
+ b. if You include all or a substantial portion of the database
+ contents in a database in which You have Sui Generis Database
+ Rights, then the database in which You have Sui Generis Database
+ Rights (but not its individual contents) is Adapted Material,
+ including for purposes of Section 3(b); and
+
+ c. You must comply with the conditions in Section 3(a) if You Share
+ all or a substantial portion of the contents of the database.
+
+For the avoidance of doubt, this Section 4 supplements and does not
+replace Your obligations under this Public License where the Licensed
+Rights include other Copyright and Similar Rights.
+
+
+Section 5 -- Disclaimer of Warranties and Limitation of Liability.
+
+ a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
+ EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
+ AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
+ ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
+ IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
+ WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
+ ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
+ KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
+ ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
+
+ b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
+ TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
+ NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
+ INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
+ COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
+ USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
+ ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
+ DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
+ IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
+
+ c. The disclaimer of warranties and limitation of liability provided
+ above shall be interpreted in a manner that, to the extent
+ possible, most closely approximates an absolute disclaimer and
+ waiver of all liability.
+
+
+Section 6 -- Term and Termination.
+
+ a. This Public License applies for the term of the Copyright and
+ Similar Rights licensed here. However, if You fail to comply with
+ this Public License, then Your rights under this Public License
+ terminate automatically.
+
+ b. Where Your right to use the Licensed Material has terminated under
+ Section 6(a), it reinstates:
+
+ 1. automatically as of the date the violation is cured, provided
+ it is cured within 30 days of Your discovery of the
+ violation; or
+
+ 2. upon express reinstatement by the Licensor.
+
+ For the avoidance of doubt, this Section 6(b) does not affect any
+ right the Licensor may have to seek remedies for Your violations
+ of this Public License.
+
+ c. For the avoidance of doubt, the Licensor may also offer the
+ Licensed Material under separate terms or conditions or stop
+ distributing the Licensed Material at any time; however, doing so
+ will not terminate this Public License.
+
+ d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
+ License.
+
+
+Section 7 -- Other Terms and Conditions.
+
+ a. The Licensor shall not be bound by any additional or different
+ terms or conditions communicated by You unless expressly agreed.
+
+ b. Any arrangements, understandings, or agreements regarding the
+ Licensed Material not stated herein are separate from and
+ independent of the terms and conditions of this Public License.
+
+
+Section 8 -- Interpretation.
+
+ a. For the avoidance of doubt, this Public License does not, and
+ shall not be interpreted to, reduce, limit, restrict, or impose
+ conditions on any use of the Licensed Material that could lawfully
+ be made without permission under this Public License.
+
+ b. To the extent possible, if any provision of this Public License is
+ deemed unenforceable, it shall be automatically reformed to the
+ minimum extent necessary to make it enforceable. If the provision
+ cannot be reformed, it shall be severed from this Public License
+ without affecting the enforceability of the remaining terms and
+ conditions.
+
+ c. No term or condition of this Public License will be waived and no
+ failure to comply consented to unless expressly agreed to by the
+ Licensor.
+
+ d. Nothing in this Public License constitutes or may be interpreted
+ as a limitation upon, or waiver of, any privileges and immunities
+ that apply to the Licensor or You, including from the legal
+ processes of any jurisdiction or authority.
+
+=======================================================================
+
+Creative Commons is not a party to its public
+licenses. Notwithstanding, Creative Commons may elect to apply one of
+its public licenses to material it publishes and in those instances
+will be considered the “Licensor.” The text of the Creative Commons
+public licenses is dedicated to the public domain under the CC0 Public
+Domain Dedication. Except for the limited purpose of indicating that
+material is shared under a Creative Commons public license or as
+otherwise permitted by the Creative Commons policies published at
+creativecommons.org/policies, Creative Commons does not authorize the
+use of the trademark "Creative Commons" or any other trademark or logo
+of Creative Commons without its prior written consent including,
+without limitation, in connection with any unauthorized modifications
+to any of its public licenses or any other arrangements,
+understandings, or agreements concerning use of licensed material. For
+the avoidance of doubt, this paragraph does not form part of the
+public licenses.
+
+Creative Commons may be contacted at creativecommons.org.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..67994bb
--- /dev/null
+++ b/README.md
@@ -0,0 +1,21 @@
+# OpenHotel Asset Editor
+
+[![Static Badge](https://img.shields.io/badge/CC_BY--NC--SA_4.0-blue?style=for-the-badge&color=gray)](/LICENSE)
+![GitHub branch check runs](https://img.shields.io/github/check-runs/openhotel/auth/master?style=for-the-badge)
+[![GitHub Release](https://img.shields.io/github/v/release/openhotel/asset-editor?style=for-the-badge)](https://github.com/openhotel/asset-editor/releases/latest)
+[![Static Badge](https://img.shields.io/badge/discord-b?style=for-the-badge&logo=discord&color=white)](https://discord.gg/qBZfPdNWUj)
+
+---
+
+## How to run the project
+
+### Dependencies
+
+- Install `deno >= 1.44`
+- Install `node >= 20`
+- Install `yarn`
+-
+
+### Start project
+
+- Run `deno task start`
diff --git a/app/client/.gitignore b/app/client/.gitignore
new file mode 100644
index 0000000..fb1fb20
--- /dev/null
+++ b/app/client/.gitignore
@@ -0,0 +1,3 @@
+.env
+/build
+node_modules
\ No newline at end of file
diff --git a/app/client/package.json b/app/client/package.json
new file mode 100644
index 0000000..48fabfe
--- /dev/null
+++ b/app/client/package.json
@@ -0,0 +1,27 @@
+{
+ "type": "module",
+ "license": "CC BY-NC-SA 4.0",
+ "scripts": {
+ "start": "vite --host",
+ "build": "vite build"
+ },
+ "devDependencies": {
+ "@rollup/plugin-alias": "5.1.0",
+ "@types/js-cookie": "3.0.6",
+ "@types/react": "18.3.3",
+ "vite": "5.2.13",
+ "vite-plugin-singlefile": "^2.0.2",
+ "vite-tsconfig-paths": "4.3.2"
+ },
+ "dependencies": {
+ "@vitejs/plugin-react": "^4.3.1",
+ "@vitejs/plugin-react-refresh": "1.3.6",
+ "js-cookie": "3.0.5",
+ "react": "18.3.1",
+ "react-dom": "18.3.1",
+ "react-router": "6.26.0",
+ "react-router-dom": "6.26.0",
+ "sass": "1.77.8"
+ },
+ "packageManager": "yarn@1.22.22"
+}
diff --git a/app/client/src/index.html b/app/client/src/index.html
new file mode 100644
index 0000000..51c4b87
--- /dev/null
+++ b/app/client/src/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/client/src/main.module.scss b/app/client/src/main.module.scss
new file mode 100644
index 0000000..cae9d53
--- /dev/null
+++ b/app/client/src/main.module.scss
@@ -0,0 +1,65 @@
+:root {
+ --color-primary: #f0ebe3;
+ --color-background: #121212;
+}
+
+* {
+ font-size: 1.6rem;
+ font-family: ui-sans-serif, system-ui, sans-serif;
+ box-sizing: border-box;
+
+ button {
+ border-radius: 0;
+ padding: 0;
+ border: 0;
+ }
+
+ input {
+ border: 0 solid;
+ padding: 0;
+ border: 0;
+ }
+
+ img {
+ vertical-align: middle;
+ image-rendering: crisp-edges;
+ }
+
+ h1 {
+ font-size: 3.2rem;
+ }
+ h2 {
+ font-size: 2.8rem;
+ }
+ h3 {
+ font-size: 2.2rem;
+ }
+ h4 {
+ font-size: 2rem;
+ }
+}
+
+html {
+ // Allows to use rem instead of px in a simple way, ej. 1.4rem is 14px, 2.5rem is 25px
+ font-size: 10px !important;
+
+ color: var(--color-primary);
+ background: var(--color-background);
+}
+
+body {
+ margin: 0;
+
+ height: 100vh;
+ width: 100vw;
+
+ background-attachment: fixed;
+ background-size: cover;
+ overflow-x: hidden !important;
+}
+a {
+ &:visited,
+ &:link {
+ color: var(--color-primary);
+ }
+}
diff --git a/app/client/src/main.tsx b/app/client/src/main.tsx
new file mode 100644
index 0000000..b73aeea
--- /dev/null
+++ b/app/client/src/main.tsx
@@ -0,0 +1,12 @@
+import React from "react";
+import { createRoot } from "react-dom/client";
+
+// @ts-ignore
+import "./main.module.scss";
+
+import { ApplicationComponent } from "modules/application";
+
+const domNode = document.getElementById("root");
+const root = createRoot(domNode);
+
+root.render( );
diff --git a/app/client/src/modules/application/components/application/application.component.tsx b/app/client/src/modules/application/components/application/application.component.tsx
new file mode 100644
index 0000000..a015a22
--- /dev/null
+++ b/app/client/src/modules/application/components/application/application.component.tsx
@@ -0,0 +1,6 @@
+import React from "react";
+import { RouterComponent } from "../router";
+
+export const ApplicationComponent = () => {
+ return ;
+};
diff --git a/app/client/src/modules/application/components/application/index.ts b/app/client/src/modules/application/components/application/index.ts
new file mode 100644
index 0000000..703276c
--- /dev/null
+++ b/app/client/src/modules/application/components/application/index.ts
@@ -0,0 +1 @@
+export * from "./application.component";
diff --git a/app/client/src/modules/application/components/content/content.component.tsx b/app/client/src/modules/application/components/content/content.component.tsx
new file mode 100644
index 0000000..12bf536
--- /dev/null
+++ b/app/client/src/modules/application/components/content/content.component.tsx
@@ -0,0 +1,10 @@
+import React from "react";
+import styles from "./content.module.scss";
+
+type Props = {
+ children?: React.ReactNode;
+};
+
+export const ContentComponent: React.FC = ({ children }) => {
+ return {children}
;
+};
diff --git a/app/client/src/modules/application/components/content/content.module.scss b/app/client/src/modules/application/components/content/content.module.scss
new file mode 100644
index 0000000..6f6ff94
--- /dev/null
+++ b/app/client/src/modules/application/components/content/content.module.scss
@@ -0,0 +1,12 @@
+@import "../../../../shared/styles/consts.module";
+
+.content {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ max-width: 200px;
+
+ .outlet {
+ flex: 1;
+ }
+}
diff --git a/app/client/src/modules/application/components/content/index.tsx b/app/client/src/modules/application/components/content/index.tsx
new file mode 100644
index 0000000..86075bd
--- /dev/null
+++ b/app/client/src/modules/application/components/content/index.tsx
@@ -0,0 +1 @@
+export * from "./content.component";
diff --git a/app/client/src/modules/application/components/footer/footer.component.tsx b/app/client/src/modules/application/components/footer/footer.component.tsx
new file mode 100644
index 0000000..c9e2e3a
--- /dev/null
+++ b/app/client/src/modules/application/components/footer/footer.component.tsx
@@ -0,0 +1,11 @@
+import React from "react";
+import styles from "./footer.module.scss";
+import { ContainerComponent } from "shared/components";
+
+export const FooterComponent = () => {
+ return (
+
+ );
+};
diff --git a/app/client/src/modules/application/components/footer/footer.module.scss b/app/client/src/modules/application/components/footer/footer.module.scss
new file mode 100644
index 0000000..95ca357
--- /dev/null
+++ b/app/client/src/modules/application/components/footer/footer.module.scss
@@ -0,0 +1,5 @@
+@import "../../../../shared/styles/consts.module";
+@import "../../../../shared/styles/mixins.module";
+
+.footer {
+}
diff --git a/app/client/src/modules/application/components/footer/index.ts b/app/client/src/modules/application/components/footer/index.ts
new file mode 100644
index 0000000..a311fe1
--- /dev/null
+++ b/app/client/src/modules/application/components/footer/index.ts
@@ -0,0 +1 @@
+export * from "./footer.component";
diff --git a/app/client/src/modules/application/components/header/header.component.tsx b/app/client/src/modules/application/components/header/header.component.tsx
new file mode 100644
index 0000000..a7f7709
--- /dev/null
+++ b/app/client/src/modules/application/components/header/header.component.tsx
@@ -0,0 +1,11 @@
+import React from "react";
+import styles from "./header.module.scss";
+import { ContainerComponent } from "shared/components";
+
+export const HeaderComponent = () => {
+ return (
+
+ );
+};
diff --git a/app/client/src/modules/application/components/header/header.module.scss b/app/client/src/modules/application/components/header/header.module.scss
new file mode 100644
index 0000000..e6ebfb9
--- /dev/null
+++ b/app/client/src/modules/application/components/header/header.module.scss
@@ -0,0 +1,5 @@
+@import "../../../../shared/styles/consts.module";
+@import "../../../../shared/styles/mixins.module";
+
+.header {
+}
diff --git a/app/client/src/modules/application/components/header/index.ts b/app/client/src/modules/application/components/header/index.ts
new file mode 100644
index 0000000..c2d38d3
--- /dev/null
+++ b/app/client/src/modules/application/components/header/index.ts
@@ -0,0 +1 @@
+export * from "./header.component";
diff --git a/app/client/src/modules/application/components/index.ts b/app/client/src/modules/application/components/index.ts
new file mode 100644
index 0000000..a326cd2
--- /dev/null
+++ b/app/client/src/modules/application/components/index.ts
@@ -0,0 +1,7 @@
+export * from "./application";
+export * from "./router";
+export * from "./not-found";
+export * from "./header";
+export * from "./layout";
+export * from "./content";
+export * from "./footer";
diff --git a/app/client/src/modules/application/components/layout/index.ts b/app/client/src/modules/application/components/layout/index.ts
new file mode 100644
index 0000000..6994a4f
--- /dev/null
+++ b/app/client/src/modules/application/components/layout/index.ts
@@ -0,0 +1 @@
+export * from "./layout.component";
diff --git a/app/client/src/modules/application/components/layout/layout.component.tsx b/app/client/src/modules/application/components/layout/layout.component.tsx
new file mode 100644
index 0000000..95d4869
--- /dev/null
+++ b/app/client/src/modules/application/components/layout/layout.component.tsx
@@ -0,0 +1,19 @@
+import React from "react";
+import { Outlet } from "react-router-dom";
+import { ContentComponent, FooterComponent } from "modules/application";
+import { HeaderComponent } from "../header";
+import styles from "./layout.module.scss";
+
+export const LayoutComponent = () => {
+ return (
+ <>
+
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/app/client/src/modules/application/components/layout/layout.module.scss b/app/client/src/modules/application/components/layout/layout.module.scss
new file mode 100644
index 0000000..e69de29
diff --git a/app/client/src/modules/application/components/not-found/index.ts b/app/client/src/modules/application/components/not-found/index.ts
new file mode 100644
index 0000000..440736b
--- /dev/null
+++ b/app/client/src/modules/application/components/not-found/index.ts
@@ -0,0 +1 @@
+export * from "modules/application/components/not-found/not-found.component";
diff --git a/app/client/src/modules/application/components/not-found/not-found.component.tsx b/app/client/src/modules/application/components/not-found/not-found.component.tsx
new file mode 100644
index 0000000..50697ee
--- /dev/null
+++ b/app/client/src/modules/application/components/not-found/not-found.component.tsx
@@ -0,0 +1,11 @@
+import React from "react";
+import { ContainerComponent } from "shared/components";
+
+export const NotFoundComponent = () => {
+ return (
+
+ 404
+ Page not found
+
+ );
+};
diff --git a/app/client/src/modules/application/components/router/index.ts b/app/client/src/modules/application/components/router/index.ts
new file mode 100644
index 0000000..bfd1a09
--- /dev/null
+++ b/app/client/src/modules/application/components/router/index.ts
@@ -0,0 +1 @@
+export * from "./router.component";
diff --git a/app/client/src/modules/application/components/router/router.component.tsx b/app/client/src/modules/application/components/router/router.component.tsx
new file mode 100644
index 0000000..4284495
--- /dev/null
+++ b/app/client/src/modules/application/components/router/router.component.tsx
@@ -0,0 +1,28 @@
+import { RouterProvider, createBrowserRouter } from "react-router-dom";
+import React from "react";
+import { LayoutComponent } from "../layout";
+import { NotFoundComponent } from "../not-found";
+import { HomeComponent } from "modules/home";
+import { RedirectComponent } from "shared/components";
+
+const router = createBrowserRouter([
+ {
+ element: ,
+ path: "/",
+ children: [
+ {
+ path: "/",
+ Component: () => ,
+ },
+ {
+ path: "/404",
+ Component: () => ,
+ },
+ { path: "*", Component: () => },
+ ],
+ },
+]);
+
+export const RouterComponent: React.FC = ({ children }) => (
+ ${children}
+);
diff --git a/app/client/src/modules/application/index.ts b/app/client/src/modules/application/index.ts
new file mode 100644
index 0000000..40b494c
--- /dev/null
+++ b/app/client/src/modules/application/index.ts
@@ -0,0 +1 @@
+export * from "./components";
diff --git a/app/client/src/modules/home/home.component.tsx b/app/client/src/modules/home/home.component.tsx
new file mode 100644
index 0000000..3166f42
--- /dev/null
+++ b/app/client/src/modules/home/home.component.tsx
@@ -0,0 +1,5 @@
+import React from "react";
+
+export const HomeComponent: React.FC = () => {
+ return Home!
;
+};
diff --git a/app/client/src/modules/home/index.ts b/app/client/src/modules/home/index.ts
new file mode 100644
index 0000000..d7102cd
--- /dev/null
+++ b/app/client/src/modules/home/index.ts
@@ -0,0 +1 @@
+export * from "./home.component";
diff --git a/app/client/src/shared/components/container/container.component.tsx b/app/client/src/shared/components/container/container.component.tsx
new file mode 100644
index 0000000..22dbfc8
--- /dev/null
+++ b/app/client/src/shared/components/container/container.component.tsx
@@ -0,0 +1,13 @@
+import React from "react";
+import styles from "./container.module.scss";
+import { cn } from "shared/utils";
+
+type Props = {
+ children?: React.ReactNode;
+ className?: string;
+};
+
+export const ContainerComponent: React.FC = ({
+ children,
+ className = "",
+}) => {children}
;
diff --git a/app/client/src/shared/components/container/container.module.scss b/app/client/src/shared/components/container/container.module.scss
new file mode 100644
index 0000000..f6adcae
--- /dev/null
+++ b/app/client/src/shared/components/container/container.module.scss
@@ -0,0 +1,6 @@
+@import "../../styles/mixins.module";
+
+.container {
+ padding: 0 2rem;
+ @include responsive-width();
+}
diff --git a/app/client/src/shared/components/container/index.ts b/app/client/src/shared/components/container/index.ts
new file mode 100644
index 0000000..b0a5427
--- /dev/null
+++ b/app/client/src/shared/components/container/index.ts
@@ -0,0 +1 @@
+export * from "./container.component";
diff --git a/app/client/src/shared/components/index.ts b/app/client/src/shared/components/index.ts
new file mode 100644
index 0000000..e6bab21
--- /dev/null
+++ b/app/client/src/shared/components/index.ts
@@ -0,0 +1,3 @@
+export * from "./container";
+export * from "./redirect";
+export * from "./link";
diff --git a/app/client/src/shared/components/link/index.ts b/app/client/src/shared/components/link/index.ts
new file mode 100644
index 0000000..c2358e6
--- /dev/null
+++ b/app/client/src/shared/components/link/index.ts
@@ -0,0 +1 @@
+export * from "./link.component";
diff --git a/app/client/src/shared/components/link/link.component.tsx b/app/client/src/shared/components/link/link.component.tsx
new file mode 100644
index 0000000..28545a4
--- /dev/null
+++ b/app/client/src/shared/components/link/link.component.tsx
@@ -0,0 +1,11 @@
+import React from "react";
+import { Link } from "react-router-dom";
+
+type Props = {
+ to: string;
+ children: React.ReactNode;
+};
+
+export const LinkComponent: React.FC = ({ to, children }) => {
+ return ;
+};
diff --git a/app/client/src/shared/components/redirect/index.ts b/app/client/src/shared/components/redirect/index.ts
new file mode 100644
index 0000000..cca8f80
--- /dev/null
+++ b/app/client/src/shared/components/redirect/index.ts
@@ -0,0 +1 @@
+export * from "./redirect.component";
diff --git a/app/client/src/shared/components/redirect/redirect.component.tsx b/app/client/src/shared/components/redirect/redirect.component.tsx
new file mode 100644
index 0000000..fed9793
--- /dev/null
+++ b/app/client/src/shared/components/redirect/redirect.component.tsx
@@ -0,0 +1,10 @@
+import React from "react";
+import { Navigate } from "react-router-dom";
+
+type Props = {
+ to: string;
+};
+
+export const RedirectComponent: React.FC = ({ to }) => {
+ return ;
+};
diff --git a/app/client/src/shared/consts/index.ts b/app/client/src/shared/consts/index.ts
new file mode 100644
index 0000000..e69de29
diff --git a/app/client/src/shared/hooks/index.ts b/app/client/src/shared/hooks/index.ts
new file mode 100644
index 0000000..cf1319c
--- /dev/null
+++ b/app/client/src/shared/hooks/index.ts
@@ -0,0 +1 @@
+export * from "./useApi";
diff --git a/app/client/src/shared/hooks/useApi.ts b/app/client/src/shared/hooks/useApi.ts
new file mode 100644
index 0000000..92127d7
--- /dev/null
+++ b/app/client/src/shared/hooks/useApi.ts
@@ -0,0 +1,147 @@
+import Cookies from "js-cookie";
+import { redirectToFallbackRedirectUrl } from "shared/utils/urls.utils";
+
+export const useApi = () => {
+ // const getTicketId = () => new URLSearchParams(location.hash).get("#ticketId");
+ //
+ // const clearSessionCookies = () => {
+ // Cookies.remove("sessionId");
+ // Cookies.remove("refreshToken");
+ // };
+ //
+ // const setFallbackRedirectUrl = (redirectUrl: string) => {
+ // const { href, search } = new URL(redirectUrl);
+ // localStorage.setItem("fallbackRedirectUrl", href.replace(search, ""));
+ // };
+ //
+ // const login = (email: string, password: string, captchaId: string) =>
+ // new Promise((resolve, reject) => {
+ // fetch("/api/v2/account/login", {
+ // method: "POST",
+ // body: JSON.stringify({
+ // ticketId: getTicketId(),
+ //
+ // email,
+ // password,
+ // captchaId,
+ // }),
+ // })
+ // .then((data) => data.json())
+ // .then(({ status, data }) => {
+ // if (status === 410) return redirectToFallbackRedirectUrl();
+ // if (status !== 200) return reject({ status });
+ // Cookies.set("sessionId", data.sessionId, {
+ // expires: 7,
+ // sameSite: "strict",
+ // });
+ // Cookies.set("refreshToken", data.refreshToken, {
+ // expires: 7,
+ // sameSite: "strict",
+ // });
+ // setFallbackRedirectUrl(data.redirectUrl);
+ // resolve(data);
+ // })
+ // .catch(() => reject({ status: 600 }));
+ // });
+ //
+ // const refreshSession = () =>
+ // new Promise((resolve, reject) => {
+ // const sessionId = Cookies.get("sessionId");
+ // const refreshToken = Cookies.get("refreshToken");
+ //
+ // if (!sessionId || !refreshToken) return reject();
+ //
+ // fetch("/api/v2/account/refresh-session", {
+ // method: "POST",
+ // body: JSON.stringify({
+ // ticketId: getTicketId(),
+ //
+ // sessionId,
+ // refreshToken,
+ // }),
+ // })
+ // .then((data) => data.json())
+ // .then(({ status, data }) => {
+ // if (status === 410) return redirectToFallbackRedirectUrl();
+ // if (status === 200) {
+ // Cookies.set("sessionId", sessionId, {
+ // expires: 7,
+ // sameSite: "strict",
+ // });
+ // Cookies.set("refreshToken", data.refreshToken, {
+ // expires: 7,
+ // sameSite: "strict",
+ // });
+ // setFallbackRedirectUrl(data.redirectUrl);
+ // return resolve(data);
+ // }
+ // clearSessionCookies();
+ // reject({ status });
+ // })
+ // .catch(() => reject({ status: 600 }));
+ // });
+ //
+ // const register = (
+ // email: string,
+ // username: string,
+ // password: string,
+ // rePassword: string,
+ // captchaId: string,
+ // ) =>
+ // new Promise((resolve, reject) => {
+ // fetch("/api/v2/account/register", {
+ // method: "POST",
+ // body: JSON.stringify({
+ // email,
+ // username,
+ // password,
+ // rePassword,
+ // captchaId,
+ // }),
+ // })
+ // .then((data) => data.json())
+ // .then(({ status }) => (status === 200 ? resolve() : reject({ status })))
+ // .catch(() => reject({ status: 600 }));
+ // });
+ //
+ // const logout = () =>
+ // new Promise((resolve) => {
+ // const sessionId = Cookies.get("sessionId");
+ // const refreshToken = Cookies.get("refreshToken");
+ //
+ // clearSessionCookies();
+ //
+ // if (!sessionId || !refreshToken) return resolve({});
+ //
+ // fetch("/api/v2/account/logout", {
+ // method: "POST",
+ // body: JSON.stringify({
+ // sessionId,
+ // refreshToken,
+ // }),
+ // })
+ // .then((data) => data.json())
+ // .then(resolve)
+ // .catch(resolve);
+ // });
+ //
+ // const verify = (id: string, token: string) =>
+ // new Promise((resolve, reject) => {
+ // if (!id || !token) return reject({ status: 700 });
+ //
+ // fetch(`/api/v2/account/verify?id=${id}&token=${token}`)
+ // .then((data) => data.json())
+ // .then(({ status }) => (status === 200 ? resolve() : reject({ status })))
+ // .catch(() => reject({ status: 600 }));
+ // });
+
+ return {
+ // login,
+ // refreshSession,
+ // register,
+ // logout,
+ // clearSessionCookies,
+ // getTicketId,
+ // verify,
+ };
+};
diff --git a/app/client/src/shared/styles/consts.module.scss b/app/client/src/shared/styles/consts.module.scss
new file mode 100644
index 0000000..55cdfe0
--- /dev/null
+++ b/app/client/src/shared/styles/consts.module.scss
@@ -0,0 +1,3 @@
+$PHONE_MAX_WIDTH: 390px;
+$TABLET_MIN_WIDTH: 768px;
+$WEB_MIN_WIDTH: 1278px;
diff --git a/app/client/src/shared/styles/mixins.module.scss b/app/client/src/shared/styles/mixins.module.scss
new file mode 100644
index 0000000..c85d6ce
--- /dev/null
+++ b/app/client/src/shared/styles/mixins.module.scss
@@ -0,0 +1,34 @@
+@import "./consts.module";
+
+@mixin web-only {
+ @media only screen and (min-width: $WEB_MIN_WIDTH) {
+ @content;
+ }
+}
+@mixin tablet-only {
+ @media only screen and (min-width: $TABLET_MIN_WIDTH) and (max-width: calc($WEB_MIN_WIDTH - 1px)) {
+ @content;
+ }
+}
+@mixin phone-only {
+ @media only screen and (max-width: calc($TABLET_MIN_WIDTH - 1px)) {
+ @content;
+ }
+}
+
+@mixin tablet {
+ @media only screen and (max-width: calc($WEB_MIN_WIDTH - 1px)) {
+ @content;
+ }
+}
+
+@mixin responsive-width {
+ max-width: $WEB_MIN_WIDTH !important;
+ margin: auto;
+ @include tablet-only() {
+ width: $TABLET_MIN_WIDTH !important;
+ }
+ @include phone-only() {
+ width: 100% !important;
+ }
+}
diff --git a/app/client/src/shared/utils/class-name.utils.ts b/app/client/src/shared/utils/class-name.utils.ts
new file mode 100644
index 0000000..d4a39f6
--- /dev/null
+++ b/app/client/src/shared/utils/class-name.utils.ts
@@ -0,0 +1,18 @@
+export const cn = (
+ ...args: Array<
+ string | { [key: string]: boolean | undefined } | undefined | null
+ >
+): string =>
+ args
+ .reduce((acc, arg: any) => {
+ if (arg === undefined) return acc;
+ if (typeof arg === "string") return [...acc, arg];
+
+ const classes = Object.keys(arg).reduce((j, key) => {
+ if (arg[key]) return [...j, key];
+ return j;
+ }, [] as string[]);
+
+ return [...acc, ...classes];
+ }, [] as string[])
+ .join(" ");
diff --git a/app/client/src/shared/utils/environment.utils.ts b/app/client/src/shared/utils/environment.utils.ts
new file mode 100644
index 0000000..725c787
--- /dev/null
+++ b/app/client/src/shared/utils/environment.utils.ts
@@ -0,0 +1,4 @@
+declare const __APP_VERSION: string;
+
+export const getVersion = () => __APP_VERSION || "DEVELOPMENT";
+export const isDevelopment = () => getVersion() === "DEVELOPMENT";
diff --git a/app/client/src/shared/utils/index.ts b/app/client/src/shared/utils/index.ts
new file mode 100644
index 0000000..7a6e948
--- /dev/null
+++ b/app/client/src/shared/utils/index.ts
@@ -0,0 +1,2 @@
+export * from "./class-name.utils";
+export * from "./environment.utils";
diff --git a/app/client/tsconfig.json b/app/client/tsconfig.json
new file mode 100644
index 0000000..d02c096
--- /dev/null
+++ b/app/client/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "compilerOptions": {
+ "moduleResolution": "node",
+ "module": "es2022",
+ "target": "es2022",
+ "esModuleInterop": true,
+ "sourceMap": false,
+ "noUnusedLocals": true,
+ "lib": ["es2023", "DOM"],
+ "baseUrl": "./src",
+ "rootDir": "./src",
+ "jsx": "react"
+ }
+}
diff --git a/app/client/vite.config.ts b/app/client/vite.config.ts
new file mode 100644
index 0000000..a735bb3
--- /dev/null
+++ b/app/client/vite.config.ts
@@ -0,0 +1,30 @@
+import { defineConfig } from "vite";
+import reactRefresh from "@vitejs/plugin-react-refresh";
+import react from "@vitejs/plugin-react";
+import { viteSingleFile } from "vite-plugin-singlefile";
+
+export default defineConfig({
+ server: {
+ port: 2030,
+ proxy: {
+ "/api": "http://localhost:20300",
+ },
+ },
+ plugins: [react(), reactRefresh(), viteSingleFile()],
+ root: "./src",
+ base: "/",
+ build: {
+ outDir: "../build",
+ emptyOutDir: true, // also necessary
+ },
+ resolve: {
+ alias: {
+ modules: "/modules",
+ shared: "/shared",
+ },
+ },
+ define: {
+ //@ts-ignore
+ __APP_VERSION: JSON.stringify(process.env.npm_package_version),
+ },
+});
diff --git a/app/client/yarn.lock b/app/client/yarn.lock
new file mode 100644
index 0000000..a64aea5
--- /dev/null
+++ b/app/client/yarn.lock
@@ -0,0 +1,967 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@ampproject/remapping@^2.2.0":
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4"
+ integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.5"
+ "@jridgewell/trace-mapping" "^0.3.24"
+
+"@babel/code-frame@^7.24.7":
+ version "7.24.7"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465"
+ integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==
+ dependencies:
+ "@babel/highlight" "^7.24.7"
+ picocolors "^1.0.0"
+
+"@babel/compat-data@^7.25.2":
+ version "7.25.2"
+ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.2.tgz#e41928bd33475305c586f6acbbb7e3ade7a6f7f5"
+ integrity sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==
+
+"@babel/core@^7.14.8", "@babel/core@^7.24.5":
+ version "7.25.2"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.25.2.tgz#ed8eec275118d7613e77a352894cd12ded8eba77"
+ integrity sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==
+ dependencies:
+ "@ampproject/remapping" "^2.2.0"
+ "@babel/code-frame" "^7.24.7"
+ "@babel/generator" "^7.25.0"
+ "@babel/helper-compilation-targets" "^7.25.2"
+ "@babel/helper-module-transforms" "^7.25.2"
+ "@babel/helpers" "^7.25.0"
+ "@babel/parser" "^7.25.0"
+ "@babel/template" "^7.25.0"
+ "@babel/traverse" "^7.25.2"
+ "@babel/types" "^7.25.2"
+ convert-source-map "^2.0.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.2"
+ json5 "^2.2.3"
+ semver "^6.3.1"
+
+"@babel/generator@^7.25.0":
+ version "7.25.0"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.0.tgz#f858ddfa984350bc3d3b7f125073c9af6988f18e"
+ integrity sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==
+ dependencies:
+ "@babel/types" "^7.25.0"
+ "@jridgewell/gen-mapping" "^0.3.5"
+ "@jridgewell/trace-mapping" "^0.3.25"
+ jsesc "^2.5.1"
+
+"@babel/helper-compilation-targets@^7.25.2":
+ version "7.25.2"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz#e1d9410a90974a3a5a66e84ff55ef62e3c02d06c"
+ integrity sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==
+ dependencies:
+ "@babel/compat-data" "^7.25.2"
+ "@babel/helper-validator-option" "^7.24.8"
+ browserslist "^4.23.1"
+ lru-cache "^5.1.1"
+ semver "^6.3.1"
+
+"@babel/helper-module-imports@^7.24.7":
+ version "7.24.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz#f2f980392de5b84c3328fc71d38bd81bbb83042b"
+ integrity sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==
+ dependencies:
+ "@babel/traverse" "^7.24.7"
+ "@babel/types" "^7.24.7"
+
+"@babel/helper-module-transforms@^7.25.2":
+ version "7.25.2"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz#ee713c29768100f2776edf04d4eb23b8d27a66e6"
+ integrity sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==
+ dependencies:
+ "@babel/helper-module-imports" "^7.24.7"
+ "@babel/helper-simple-access" "^7.24.7"
+ "@babel/helper-validator-identifier" "^7.24.7"
+ "@babel/traverse" "^7.25.2"
+
+"@babel/helper-plugin-utils@^7.24.7":
+ version "7.24.8"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878"
+ integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==
+
+"@babel/helper-simple-access@^7.24.7":
+ version "7.24.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz#bcade8da3aec8ed16b9c4953b74e506b51b5edb3"
+ integrity sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==
+ dependencies:
+ "@babel/traverse" "^7.24.7"
+ "@babel/types" "^7.24.7"
+
+"@babel/helper-string-parser@^7.24.8":
+ version "7.24.8"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d"
+ integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==
+
+"@babel/helper-validator-identifier@^7.24.7":
+ version "7.24.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db"
+ integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==
+
+"@babel/helper-validator-option@^7.24.8":
+ version "7.24.8"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d"
+ integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==
+
+"@babel/helpers@^7.25.0":
+ version "7.25.0"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.0.tgz#e69beb7841cb93a6505531ede34f34e6a073650a"
+ integrity sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==
+ dependencies:
+ "@babel/template" "^7.25.0"
+ "@babel/types" "^7.25.0"
+
+"@babel/highlight@^7.24.7":
+ version "7.24.7"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d"
+ integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.24.7"
+ chalk "^2.4.2"
+ js-tokens "^4.0.0"
+ picocolors "^1.0.0"
+
+"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.25.0", "@babel/parser@^7.25.3":
+ version "7.25.3"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.3.tgz#91fb126768d944966263f0657ab222a642b82065"
+ integrity sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==
+ dependencies:
+ "@babel/types" "^7.25.2"
+
+"@babel/plugin-transform-react-jsx-self@^7.14.5", "@babel/plugin-transform-react-jsx-self@^7.24.5":
+ version "7.24.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz#66bff0248ea0b549972e733516ffad577477bdab"
+ integrity sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.24.7"
+
+"@babel/plugin-transform-react-jsx-source@^7.14.5", "@babel/plugin-transform-react-jsx-source@^7.24.1":
+ version "7.24.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.7.tgz#1198aab2548ad19582013815c938d3ebd8291ee3"
+ integrity sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.24.7"
+
+"@babel/template@^7.25.0":
+ version "7.25.0"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.0.tgz#e733dc3134b4fede528c15bc95e89cb98c52592a"
+ integrity sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==
+ dependencies:
+ "@babel/code-frame" "^7.24.7"
+ "@babel/parser" "^7.25.0"
+ "@babel/types" "^7.25.0"
+
+"@babel/traverse@^7.24.7", "@babel/traverse@^7.25.2":
+ version "7.25.3"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.3.tgz#f1b901951c83eda2f3e29450ce92743783373490"
+ integrity sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==
+ dependencies:
+ "@babel/code-frame" "^7.24.7"
+ "@babel/generator" "^7.25.0"
+ "@babel/parser" "^7.25.3"
+ "@babel/template" "^7.25.0"
+ "@babel/types" "^7.25.2"
+ debug "^4.3.1"
+ globals "^11.1.0"
+
+"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.24.7", "@babel/types@^7.25.0", "@babel/types@^7.25.2":
+ version "7.25.2"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.2.tgz#55fb231f7dc958cd69ea141a4c2997e819646125"
+ integrity sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==
+ dependencies:
+ "@babel/helper-string-parser" "^7.24.8"
+ "@babel/helper-validator-identifier" "^7.24.7"
+ to-fast-properties "^2.0.0"
+
+"@esbuild/aix-ppc64@0.20.2":
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537"
+ integrity sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==
+
+"@esbuild/android-arm64@0.20.2":
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz#db1c9202a5bc92ea04c7b6840f1bbe09ebf9e6b9"
+ integrity sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==
+
+"@esbuild/android-arm@0.20.2":
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.2.tgz#3b488c49aee9d491c2c8f98a909b785870d6e995"
+ integrity sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==
+
+"@esbuild/android-x64@0.20.2":
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.2.tgz#3b1628029e5576249d2b2d766696e50768449f98"
+ integrity sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==
+
+"@esbuild/darwin-arm64@0.20.2":
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz#6e8517a045ddd86ae30c6608c8475ebc0c4000bb"
+ integrity sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==
+
+"@esbuild/darwin-x64@0.20.2":
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz#90ed098e1f9dd8a9381695b207e1cff45540a0d0"
+ integrity sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==
+
+"@esbuild/freebsd-arm64@0.20.2":
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz#d71502d1ee89a1130327e890364666c760a2a911"
+ integrity sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==
+
+"@esbuild/freebsd-x64@0.20.2":
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz#aa5ea58d9c1dd9af688b8b6f63ef0d3d60cea53c"
+ integrity sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==
+
+"@esbuild/linux-arm64@0.20.2":
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz#055b63725df678379b0f6db9d0fa85463755b2e5"
+ integrity sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==
+
+"@esbuild/linux-arm@0.20.2":
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz#76b3b98cb1f87936fbc37f073efabad49dcd889c"
+ integrity sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==
+
+"@esbuild/linux-ia32@0.20.2":
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz#c0e5e787c285264e5dfc7a79f04b8b4eefdad7fa"
+ integrity sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==
+
+"@esbuild/linux-loong64@0.20.2":
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz#a6184e62bd7cdc63e0c0448b83801001653219c5"
+ integrity sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==
+
+"@esbuild/linux-mips64el@0.20.2":
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz#d08e39ce86f45ef8fc88549d29c62b8acf5649aa"
+ integrity sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==
+
+"@esbuild/linux-ppc64@0.20.2":
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz#8d252f0b7756ffd6d1cbde5ea67ff8fd20437f20"
+ integrity sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==
+
+"@esbuild/linux-riscv64@0.20.2":
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz#19f6dcdb14409dae607f66ca1181dd4e9db81300"
+ integrity sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==
+
+"@esbuild/linux-s390x@0.20.2":
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz#3c830c90f1a5d7dd1473d5595ea4ebb920988685"
+ integrity sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==
+
+"@esbuild/linux-x64@0.20.2":
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz#86eca35203afc0d9de0694c64ec0ab0a378f6fff"
+ integrity sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==
+
+"@esbuild/netbsd-x64@0.20.2":
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz#e771c8eb0e0f6e1877ffd4220036b98aed5915e6"
+ integrity sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==
+
+"@esbuild/openbsd-x64@0.20.2":
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz#9a795ae4b4e37e674f0f4d716f3e226dd7c39baf"
+ integrity sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==
+
+"@esbuild/sunos-x64@0.20.2":
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz#7df23b61a497b8ac189def6e25a95673caedb03f"
+ integrity sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==
+
+"@esbuild/win32-arm64@0.20.2":
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz#f1ae5abf9ca052ae11c1bc806fb4c0f519bacf90"
+ integrity sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==
+
+"@esbuild/win32-ia32@0.20.2":
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz#241fe62c34d8e8461cd708277813e1d0ba55ce23"
+ integrity sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==
+
+"@esbuild/win32-x64@0.20.2":
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz#9c907b21e30a52db959ba4f80bb01a0cc403d5cc"
+ integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==
+
+"@jridgewell/gen-mapping@^0.3.5":
+ version "0.3.5"
+ resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36"
+ integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==
+ dependencies:
+ "@jridgewell/set-array" "^1.2.1"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+ "@jridgewell/trace-mapping" "^0.3.24"
+
+"@jridgewell/resolve-uri@^3.1.0":
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6"
+ integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
+
+"@jridgewell/set-array@^1.2.1":
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280"
+ integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==
+
+"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14":
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a"
+ integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==
+
+"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25":
+ version "0.3.25"
+ resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0"
+ integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==
+ dependencies:
+ "@jridgewell/resolve-uri" "^3.1.0"
+ "@jridgewell/sourcemap-codec" "^1.4.14"
+
+"@remix-run/router@1.19.0":
+ version "1.19.0"
+ resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.19.0.tgz#745dbffbce67f05386d57ca22c51dfd85c979593"
+ integrity sha512-zDICCLKEwbVYTS6TjYaWtHXxkdoUvD/QXvyVZjGCsWz5vyH7aFeONlPffPdW+Y/t6KT0MgXb2Mfjun9YpWN1dA==
+
+"@rollup/plugin-alias@5.1.0":
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/@rollup/plugin-alias/-/plugin-alias-5.1.0.tgz#99a94accc4ff9a3483be5baeedd5d7da3b597e93"
+ integrity sha512-lpA3RZ9PdIG7qqhEfv79tBffNaoDuukFDrmhLqg9ifv99u/ehn+lOg30x2zmhf8AQqQUZaMk/B9fZraQ6/acDQ==
+ dependencies:
+ slash "^4.0.0"
+
+"@rollup/pluginutils@^4.1.1":
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d"
+ integrity sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==
+ dependencies:
+ estree-walker "^2.0.1"
+ picomatch "^2.2.2"
+
+"@rollup/rollup-android-arm-eabi@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz#c3f5660f67030c493a981ac1d34ee9dfe1d8ec0f"
+ integrity sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==
+
+"@rollup/rollup-android-arm64@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.20.0.tgz#64161f0b67050023a3859e723570af54a82cff5c"
+ integrity sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==
+
+"@rollup/rollup-darwin-arm64@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz#25f3d57b1da433097cfebc89341b355901615763"
+ integrity sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==
+
+"@rollup/rollup-darwin-x64@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.20.0.tgz#d8ddaffb636cc2f59222c50316e27771e48966df"
+ integrity sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==
+
+"@rollup/rollup-linux-arm-gnueabihf@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.20.0.tgz#41bd4fcffa20fb84f3dbac6c5071638f46151885"
+ integrity sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==
+
+"@rollup/rollup-linux-arm-musleabihf@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.20.0.tgz#842077c5113a747eb5686f19f2f18c33ecc0acc8"
+ integrity sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==
+
+"@rollup/rollup-linux-arm64-gnu@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz#65d1d5b6778848f55b7823958044bf3e8737e5b7"
+ integrity sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==
+
+"@rollup/rollup-linux-arm64-musl@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.20.0.tgz#50eef7d6e24d0fe3332200bb666cad2be8afcf86"
+ integrity sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==
+
+"@rollup/rollup-linux-powerpc64le-gnu@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.20.0.tgz#8837e858f53c84607f05ad0602943e96d104c6b4"
+ integrity sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==
+
+"@rollup/rollup-linux-riscv64-gnu@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.20.0.tgz#c894ade2300caa447757ddf45787cca246e816a4"
+ integrity sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==
+
+"@rollup/rollup-linux-s390x-gnu@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.20.0.tgz#5841e5390d4c82dd5cdf7b2c95a830e3c2f47dd3"
+ integrity sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==
+
+"@rollup/rollup-linux-x64-gnu@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.20.0.tgz#cc1f26398bf777807a99226dc13f47eb0f6c720d"
+ integrity sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==
+
+"@rollup/rollup-linux-x64-musl@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.20.0.tgz#1507465d9056e0502a590d4c1a00b4d7b1fda370"
+ integrity sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==
+
+"@rollup/rollup-win32-arm64-msvc@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.20.0.tgz#86a221f01a2c248104dd0defb4da119f2a73642e"
+ integrity sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==
+
+"@rollup/rollup-win32-ia32-msvc@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.20.0.tgz#8bc8f77e02760aa664694b4286d6fbea7f1331c5"
+ integrity sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==
+
+"@rollup/rollup-win32-x64-msvc@4.20.0":
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz#601fffee719a1e8447f908aca97864eec23b2784"
+ integrity sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==
+
+"@types/babel__core@^7.20.5":
+ version "7.20.5"
+ resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017"
+ integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==
+ dependencies:
+ "@babel/parser" "^7.20.7"
+ "@babel/types" "^7.20.7"
+ "@types/babel__generator" "*"
+ "@types/babel__template" "*"
+ "@types/babel__traverse" "*"
+
+"@types/babel__generator@*":
+ version "7.6.8"
+ resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab"
+ integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==
+ dependencies:
+ "@babel/types" "^7.0.0"
+
+"@types/babel__template@*":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f"
+ integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==
+ dependencies:
+ "@babel/parser" "^7.1.0"
+ "@babel/types" "^7.0.0"
+
+"@types/babel__traverse@*":
+ version "7.20.6"
+ resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.6.tgz#8dc9f0ae0f202c08d8d4dab648912c8d6038e3f7"
+ integrity sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==
+ dependencies:
+ "@babel/types" "^7.20.7"
+
+"@types/estree@1.0.5":
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
+ integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
+
+"@types/js-cookie@3.0.6":
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-3.0.6.tgz#a04ca19e877687bd449f5ad37d33b104b71fdf95"
+ integrity sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==
+
+"@types/prop-types@*":
+ version "15.7.12"
+ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6"
+ integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==
+
+"@types/react@18.3.3":
+ version "18.3.3"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.3.tgz#9679020895318b0915d7a3ab004d92d33375c45f"
+ integrity sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==
+ dependencies:
+ "@types/prop-types" "*"
+ csstype "^3.0.2"
+
+"@vitejs/plugin-react-refresh@1.3.6":
+ version "1.3.6"
+ resolved "https://registry.yarnpkg.com/@vitejs/plugin-react-refresh/-/plugin-react-refresh-1.3.6.tgz#19818392db01e81746cfeb04e096ab3010e79fe3"
+ integrity sha512-iNR/UqhUOmFFxiezt0em9CgmiJBdWR+5jGxB2FihaoJfqGt76kiwaKoVOJVU5NYcDWMdN06LbyN2VIGIoYdsEA==
+ dependencies:
+ "@babel/core" "^7.14.8"
+ "@babel/plugin-transform-react-jsx-self" "^7.14.5"
+ "@babel/plugin-transform-react-jsx-source" "^7.14.5"
+ "@rollup/pluginutils" "^4.1.1"
+ react-refresh "^0.10.0"
+
+"@vitejs/plugin-react@^4.3.1":
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz#d0be6594051ded8957df555ff07a991fb618b48e"
+ integrity sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==
+ dependencies:
+ "@babel/core" "^7.24.5"
+ "@babel/plugin-transform-react-jsx-self" "^7.24.5"
+ "@babel/plugin-transform-react-jsx-source" "^7.24.1"
+ "@types/babel__core" "^7.20.5"
+ react-refresh "^0.14.2"
+
+ansi-styles@^3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
+ integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
+ dependencies:
+ color-convert "^1.9.0"
+
+anymatch@~3.1.2:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
+ integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
+ dependencies:
+ normalize-path "^3.0.0"
+ picomatch "^2.0.4"
+
+binary-extensions@^2.0.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
+ integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
+
+braces@^3.0.3, braces@~3.0.2:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
+ integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
+ dependencies:
+ fill-range "^7.1.1"
+
+browserslist@^4.23.1:
+ version "4.23.3"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.3.tgz#debb029d3c93ebc97ffbc8d9cbb03403e227c800"
+ integrity sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==
+ dependencies:
+ caniuse-lite "^1.0.30001646"
+ electron-to-chromium "^1.5.4"
+ node-releases "^2.0.18"
+ update-browserslist-db "^1.1.0"
+
+caniuse-lite@^1.0.30001646:
+ version "1.0.30001651"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz#52de59529e8b02b1aedcaaf5c05d9e23c0c28138"
+ integrity sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==
+
+chalk@^2.4.2:
+ version "2.4.2"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
+ integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
+ dependencies:
+ ansi-styles "^3.2.1"
+ escape-string-regexp "^1.0.5"
+ supports-color "^5.3.0"
+
+"chokidar@>=3.0.0 <4.0.0":
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
+ integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
+ dependencies:
+ anymatch "~3.1.2"
+ braces "~3.0.2"
+ glob-parent "~5.1.2"
+ is-binary-path "~2.1.0"
+ is-glob "~4.0.1"
+ normalize-path "~3.0.0"
+ readdirp "~3.6.0"
+ optionalDependencies:
+ fsevents "~2.3.2"
+
+color-convert@^1.9.0:
+ version "1.9.3"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
+ integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
+ dependencies:
+ color-name "1.1.3"
+
+color-name@1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
+ integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
+
+convert-source-map@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
+ integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
+
+csstype@^3.0.2:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
+ integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
+
+debug@^4.1.0, debug@^4.1.1, debug@^4.3.1:
+ version "4.3.6"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b"
+ integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==
+ dependencies:
+ ms "2.1.2"
+
+electron-to-chromium@^1.5.4:
+ version "1.5.6"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.6.tgz#c81d9938b5a877314ad370feb73b4e5409b36abd"
+ integrity sha512-jwXWsM5RPf6j9dPYzaorcBSUg6AiqocPEyMpkchkvntaH9HGfOOMZwxMJjDY/XEs3T5dM7uyH1VhRMkqUU9qVw==
+
+esbuild@^0.20.1:
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.2.tgz#9d6b2386561766ee6b5a55196c6d766d28c87ea1"
+ integrity sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==
+ optionalDependencies:
+ "@esbuild/aix-ppc64" "0.20.2"
+ "@esbuild/android-arm" "0.20.2"
+ "@esbuild/android-arm64" "0.20.2"
+ "@esbuild/android-x64" "0.20.2"
+ "@esbuild/darwin-arm64" "0.20.2"
+ "@esbuild/darwin-x64" "0.20.2"
+ "@esbuild/freebsd-arm64" "0.20.2"
+ "@esbuild/freebsd-x64" "0.20.2"
+ "@esbuild/linux-arm" "0.20.2"
+ "@esbuild/linux-arm64" "0.20.2"
+ "@esbuild/linux-ia32" "0.20.2"
+ "@esbuild/linux-loong64" "0.20.2"
+ "@esbuild/linux-mips64el" "0.20.2"
+ "@esbuild/linux-ppc64" "0.20.2"
+ "@esbuild/linux-riscv64" "0.20.2"
+ "@esbuild/linux-s390x" "0.20.2"
+ "@esbuild/linux-x64" "0.20.2"
+ "@esbuild/netbsd-x64" "0.20.2"
+ "@esbuild/openbsd-x64" "0.20.2"
+ "@esbuild/sunos-x64" "0.20.2"
+ "@esbuild/win32-arm64" "0.20.2"
+ "@esbuild/win32-ia32" "0.20.2"
+ "@esbuild/win32-x64" "0.20.2"
+
+escalade@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27"
+ integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==
+
+escape-string-regexp@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
+ integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
+
+estree-walker@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
+ integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
+
+fill-range@^7.1.1:
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
+ integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
+ dependencies:
+ to-regex-range "^5.0.1"
+
+fsevents@~2.3.2, fsevents@~2.3.3:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
+ integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
+
+gensync@^1.0.0-beta.2:
+ version "1.0.0-beta.2"
+ resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
+ integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
+
+glob-parent@~5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
+ integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
+ dependencies:
+ is-glob "^4.0.1"
+
+globals@^11.1.0:
+ version "11.12.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
+ integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
+
+globrex@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098"
+ integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==
+
+has-flag@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
+ integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==
+
+immutable@^4.0.0:
+ version "4.3.7"
+ resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.7.tgz#c70145fc90d89fb02021e65c84eb0226e4e5a381"
+ integrity sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==
+
+is-binary-path@~2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
+ integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
+ dependencies:
+ binary-extensions "^2.0.0"
+
+is-extglob@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
+ integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
+
+is-glob@^4.0.1, is-glob@~4.0.1:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
+ integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
+ dependencies:
+ is-extglob "^2.1.1"
+
+is-number@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
+ integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+
+js-cookie@3.0.5:
+ version "3.0.5"
+ resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc"
+ integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==
+
+"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+ integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+
+jsesc@^2.5.1:
+ version "2.5.2"
+ resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
+ integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
+
+json5@^2.2.3:
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
+ integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
+
+loose-envify@^1.1.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
+ integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
+ dependencies:
+ js-tokens "^3.0.0 || ^4.0.0"
+
+lru-cache@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
+ integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
+ dependencies:
+ yallist "^3.0.2"
+
+micromatch@^4.0.7:
+ version "4.0.7"
+ resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5"
+ integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==
+ dependencies:
+ braces "^3.0.3"
+ picomatch "^2.3.1"
+
+ms@2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
+ integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+
+nanoid@^3.3.7:
+ version "3.3.7"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
+ integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
+
+node-releases@^2.0.18:
+ version "2.0.18"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f"
+ integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==
+
+normalize-path@^3.0.0, normalize-path@~3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
+ integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+
+picocolors@^1.0.0, picocolors@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1"
+ integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==
+
+picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
+ integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+
+postcss@^8.4.38:
+ version "8.4.41"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.41.tgz#d6104d3ba272d882fe18fc07d15dc2da62fa2681"
+ integrity sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==
+ dependencies:
+ nanoid "^3.3.7"
+ picocolors "^1.0.1"
+ source-map-js "^1.2.0"
+
+react-dom@18.3.1:
+ version "18.3.1"
+ resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4"
+ integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
+ dependencies:
+ loose-envify "^1.1.0"
+ scheduler "^0.23.2"
+
+react-refresh@^0.10.0:
+ version "0.10.0"
+ resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.10.0.tgz#2f536c9660c0b9b1d500684d9e52a65e7404f7e3"
+ integrity sha512-PgidR3wST3dDYKr6b4pJoqQFpPGNKDSCDx4cZoshjXipw3LzO7mG1My2pwEzz2JVkF+inx3xRpDeQLFQGH/hsQ==
+
+react-refresh@^0.14.2:
+ version "0.14.2"
+ resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9"
+ integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==
+
+react-router-dom@6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.26.0.tgz#8debe13295c58605c04f93018d659a763245e58c"
+ integrity sha512-RRGUIiDtLrkX3uYcFiCIxKFWMcWQGMojpYZfcstc63A1+sSnVgILGIm9gNUA6na3Fm1QuPGSBQH2EMbAZOnMsQ==
+ dependencies:
+ "@remix-run/router" "1.19.0"
+ react-router "6.26.0"
+
+react-router@6.26.0:
+ version "6.26.0"
+ resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.26.0.tgz#d5af4c46835b202348ef2b7ddacd32a2db539fde"
+ integrity sha512-wVQq0/iFYd3iZ9H2l3N3k4PL8EEHcb0XlU2Na8nEwmiXgIUElEH6gaJDtUQxJ+JFzmIXaQjfdpcGWaM6IoQGxg==
+ dependencies:
+ "@remix-run/router" "1.19.0"
+
+react@18.3.1:
+ version "18.3.1"
+ resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891"
+ integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
+ dependencies:
+ loose-envify "^1.1.0"
+
+readdirp@~3.6.0:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
+ integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
+ dependencies:
+ picomatch "^2.2.1"
+
+rollup@^4.13.0:
+ version "4.20.0"
+ resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.20.0.tgz#f9d602161d29e178f0bf1d9f35f0a26f83939492"
+ integrity sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==
+ dependencies:
+ "@types/estree" "1.0.5"
+ optionalDependencies:
+ "@rollup/rollup-android-arm-eabi" "4.20.0"
+ "@rollup/rollup-android-arm64" "4.20.0"
+ "@rollup/rollup-darwin-arm64" "4.20.0"
+ "@rollup/rollup-darwin-x64" "4.20.0"
+ "@rollup/rollup-linux-arm-gnueabihf" "4.20.0"
+ "@rollup/rollup-linux-arm-musleabihf" "4.20.0"
+ "@rollup/rollup-linux-arm64-gnu" "4.20.0"
+ "@rollup/rollup-linux-arm64-musl" "4.20.0"
+ "@rollup/rollup-linux-powerpc64le-gnu" "4.20.0"
+ "@rollup/rollup-linux-riscv64-gnu" "4.20.0"
+ "@rollup/rollup-linux-s390x-gnu" "4.20.0"
+ "@rollup/rollup-linux-x64-gnu" "4.20.0"
+ "@rollup/rollup-linux-x64-musl" "4.20.0"
+ "@rollup/rollup-win32-arm64-msvc" "4.20.0"
+ "@rollup/rollup-win32-ia32-msvc" "4.20.0"
+ "@rollup/rollup-win32-x64-msvc" "4.20.0"
+ fsevents "~2.3.2"
+
+sass@1.77.8:
+ version "1.77.8"
+ resolved "https://registry.yarnpkg.com/sass/-/sass-1.77.8.tgz#9f18b449ea401759ef7ec1752a16373e296b52bd"
+ integrity sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==
+ dependencies:
+ chokidar ">=3.0.0 <4.0.0"
+ immutable "^4.0.0"
+ source-map-js ">=0.6.2 <2.0.0"
+
+scheduler@^0.23.2:
+ version "0.23.2"
+ resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3"
+ integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==
+ dependencies:
+ loose-envify "^1.1.0"
+
+semver@^6.3.1:
+ version "6.3.1"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
+ integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
+
+slash@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7"
+ integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==
+
+"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af"
+ integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==
+
+supports-color@^5.3.0:
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
+ integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
+ dependencies:
+ has-flag "^3.0.0"
+
+to-fast-properties@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
+ integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==
+
+to-regex-range@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
+ integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
+ dependencies:
+ is-number "^7.0.0"
+
+tsconfck@^3.0.3:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-3.1.1.tgz#c7284913262c293b43b905b8b034f524de4a3162"
+ integrity sha512-00eoI6WY57SvZEVjm13stEVE90VkEdJAFGgpFLTsZbJyW/LwFQ7uQxJHWpZ2hzSWgCPKc9AnBnNP+0X7o3hAmQ==
+
+update-browserslist-db@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e"
+ integrity sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==
+ dependencies:
+ escalade "^3.1.2"
+ picocolors "^1.0.1"
+
+vite-plugin-singlefile@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/vite-plugin-singlefile/-/vite-plugin-singlefile-2.0.2.tgz#af2c95e0182bd363dbe29b80bea5c5a78209f649"
+ integrity sha512-Z2ou6HcvED5CF0hM+vcFSaFa+klyS8RyyLxW0PbMRLnMbvzTI6ueWyxdYNFhpuXZgz/aj6+E/dHFTdEcw6gb9w==
+ dependencies:
+ micromatch "^4.0.7"
+
+vite-tsconfig-paths@4.3.2:
+ version "4.3.2"
+ resolved "https://registry.yarnpkg.com/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.2.tgz#321f02e4b736a90ff62f9086467faf4e2da857a9"
+ integrity sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==
+ dependencies:
+ debug "^4.1.1"
+ globrex "^0.1.2"
+ tsconfck "^3.0.3"
+
+vite@5.2.13:
+ version "5.2.13"
+ resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.13.tgz#945ababcbe3d837ae2479c29f661cd20bc5e1a80"
+ integrity sha512-SSq1noJfY9pR3I1TUENL3rQYDQCFqgD+lM6fTRAM8Nv6Lsg5hDLaXkjETVeBt+7vZBCMoibD+6IWnT2mJ+Zb/A==
+ dependencies:
+ esbuild "^0.20.1"
+ postcss "^8.4.38"
+ rollup "^4.13.0"
+ optionalDependencies:
+ fsevents "~2.3.3"
+
+yallist@^3.0.2:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
+ integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
diff --git a/app/server/.gitignore b/app/server/.gitignore
new file mode 100644
index 0000000..23dfcb0
--- /dev/null
+++ b/app/server/.gitignore
@@ -0,0 +1,3 @@
+/build
+config.yml
+index.html
\ No newline at end of file
diff --git a/app/server/build-index.ts b/app/server/build-index.ts
new file mode 100644
index 0000000..48fbe87
--- /dev/null
+++ b/app/server/build-index.ts
@@ -0,0 +1,4 @@
+const indexData = await Deno.readTextFile("./index.html");
+
+const data = `export const clientIndex = \`${encodeURIComponent(indexData)}\``;
+await Deno.writeTextFile("./client-index.ts", data);
diff --git a/app/server/client-index.ts b/app/server/client-index.ts
new file mode 100644
index 0000000..934a3b5
--- /dev/null
+++ b/app/server/client-index.ts
@@ -0,0 +1 @@
+export const clientIndex = `{{INDEX_FILE}}`;
diff --git a/app/server/deno.json b/app/server/deno.json
new file mode 100644
index 0000000..fde105b
--- /dev/null
+++ b/app/server/deno.json
@@ -0,0 +1,29 @@
+{
+ "tasks": {
+ "start": "ENV=development deno run -A --watch=./src --unstable-kv mod.ts",
+ "start:nowatch": "ENV=development deno run -A --unstable-kv mod.ts",
+
+ "compile": "deno compile -A --unstable-kv --no-check",
+
+ "compile:windows": "deno task compile --target=x86_64-pc-windows-msvc --output ./build/assets_editor_windows ./mod.ts",
+ "compile:linux": "deno task compile --target=x86_64-unknown-linux-gnu --output ./build/assets_editor_linux ./mod.ts",
+ "compile:darwin": "deno task compile --target=x86_64-apple-darwin --output ./build/assets_editor_darwin ./mod.ts",
+
+ "build:index": "deno run -A build-index.ts",
+
+ "build": "(deno task compile:linux) & (deno task compile:darwin) & (deno task compile:windows)"
+ },
+ "imports": {
+ "shared/": "./src/shared/",
+ "system/": "./src/system/",
+ "modules/": "./src/modules/",
+
+ "deno/": "https://deno.land/std@0.221.0/",
+ "loadenv": "https://deno.land/x/loadenv@v1.0.1/mod.ts",
+
+ "yaml": "npm:yaml@2.4.2",
+
+ "bcrypt": "https://deno.land/x/bcrypt@v0.4.1/mod.ts",
+ "smtp": "https://deno.land/x/smtp@v0.7.0/mod.ts"
+ }
+}
diff --git a/app/server/deno.lock b/app/server/deno.lock
new file mode 100644
index 0000000..322545e
--- /dev/null
+++ b/app/server/deno.lock
@@ -0,0 +1,157 @@
+{
+ "version": "3",
+ "packages": {
+ "specifiers": {
+ "npm:yaml@2.4.2": "npm:yaml@2.4.2"
+ },
+ "npm": {
+ "yaml@2.4.2": {
+ "integrity": "sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==",
+ "dependencies": {}
+ }
+ }
+ },
+ "redirects": {
+ "https://deno.land/x/denomailer/mod.ts": "https://deno.land/x/denomailer@1.6.0/mod.ts",
+ "https://deno.land/x/smtp/mod.ts": "https://deno.land/x/smtp@v0.7.0/mod.ts"
+ },
+ "remote": {
+ "https://deno.land/std@0.173.0/encoding/base64.ts": "7de04c2f8aeeb41453b09b186480be90f2ff357613b988e99fabb91d2eeceba1",
+ "https://deno.land/std@0.211.0/dotenv/mod.ts": "bcfa9c102d5ce6218ec0c4a69aa0fc1009fc309a85448f98a8debaa75866a3d7",
+ "https://deno.land/std@0.211.0/dotenv/parse.ts": "ddcb04a8a7198918cfc52efbebb0d4fc51abfea913649912fcc1cf4c52bd81a2",
+ "https://deno.land/std@0.211.0/dotenv/stringify.ts": "74521a8e907adffff19f132765902a8c51743e386107b91cc934bae154031e84",
+ "https://deno.land/std@0.221.0/assert/assert.ts": "bec068b2fccdd434c138a555b19a2c2393b71dfaada02b7d568a01541e67cdc5",
+ "https://deno.land/std@0.221.0/assert/assertion_error.ts": "9f689a101ee586c4ce92f52fa7ddd362e86434ffdf1f848e45987dc7689976b8",
+ "https://deno.land/std@0.221.0/fs/_create_walk_entry.ts": "5d9d2aaec05bcf09a06748b1684224d33eba7a4de24cf4cf5599991ca6b5b412",
+ "https://deno.land/std@0.221.0/fs/_get_file_info_type.ts": "da7bec18a7661dba360a1db475b826b18977582ce6fc9b25f3d4ee0403fe8cbd",
+ "https://deno.land/std@0.221.0/fs/_is_same_path.ts": "709c95868345fea051c58b9e96af95cff94e6ae98dfcff2b66dee0c212c4221f",
+ "https://deno.land/std@0.221.0/fs/_is_subdir.ts": "c68b309d46cc8568ed83c000f608a61bbdba0943b7524e7a30f9e450cf67eecd",
+ "https://deno.land/std@0.221.0/fs/_to_path_string.ts": "29bfc9c6c112254961d75cbf6ba814d6de5349767818eb93090cecfa9665591e",
+ "https://deno.land/std@0.221.0/fs/copy.ts": "7ab12a16adb65d155d4943c88081ca16ce3b0b5acada64c1ce93800653678039",
+ "https://deno.land/std@0.221.0/fs/empty_dir.ts": "e400e96e1d2c8c558a5a1712063bd43939e00619c1d1cc29959babc6f1639418",
+ "https://deno.land/std@0.221.0/fs/ensure_dir.ts": "313e8a62b8bb20d900138ff794bde6a6ac0a6bebc91220fba6dfc3303bde56c6",
+ "https://deno.land/std@0.221.0/fs/ensure_file.ts": "67608cf550529f3d4aa1f8b6b36bf817bdc40b14487bf8f60e61cbf68f507cf3",
+ "https://deno.land/std@0.221.0/fs/ensure_link.ts": "5c98503ebfa9cc05e2f2efaa30e91e60b4dd5b43ebbda82f435c0a5c6e3ffa01",
+ "https://deno.land/std@0.221.0/fs/ensure_symlink.ts": "cafe904cebacb9a761977d6dbf5e3af938be946a723bb394080b9a52714fafe4",
+ "https://deno.land/std@0.221.0/fs/eol.ts": "18c4ac009d0318504c285879eb7f47942643f13619e0ff070a0edc59353306bd",
+ "https://deno.land/std@0.221.0/fs/exists.ts": "3d38cb7dcbca3cf313be343a7b8af18a87bddb4b5ca1bd2314be12d06533b50f",
+ "https://deno.land/std@0.221.0/fs/expand_glob.ts": "2e428d90acc6676b2aa7b5c78ef48f30641b13f1fe658e7976c9064fb4b05309",
+ "https://deno.land/std@0.221.0/fs/mod.ts": "c25e6802cbf27f3050f60b26b00c2d8dba1cb7fcdafe34c66006a7473b7b34d4",
+ "https://deno.land/std@0.221.0/fs/move.ts": "ca205d848908d7f217353bc5c623627b1333490b8b5d3ef4cab600a700c9bd8f",
+ "https://deno.land/std@0.221.0/fs/walk.ts": "cddf87d2705c0163bff5d7767291f05b0f46ba10b8b28f227c3849cace08d303",
+ "https://deno.land/std@0.221.0/path/_common/assert_path.ts": "dbdd757a465b690b2cc72fc5fb7698c51507dec6bfafce4ca500c46b76ff7bd8",
+ "https://deno.land/std@0.221.0/path/_common/basename.ts": "569744855bc8445f3a56087fd2aed56bdad39da971a8d92b138c9913aecc5fa2",
+ "https://deno.land/std@0.221.0/path/_common/common.ts": "ef73c2860694775fe8ffcbcdd387f9f97c7a656febf0daa8c73b56f4d8a7bd4c",
+ "https://deno.land/std@0.221.0/path/_common/constants.ts": "dc5f8057159f4b48cd304eb3027e42f1148cf4df1fb4240774d3492b5d12ac0c",
+ "https://deno.land/std@0.221.0/path/_common/dirname.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8",
+ "https://deno.land/std@0.221.0/path/_common/format.ts": "92500e91ea5de21c97f5fe91e178bae62af524b72d5fcd246d6d60ae4bcada8b",
+ "https://deno.land/std@0.221.0/path/_common/from_file_url.ts": "d672bdeebc11bf80e99bf266f886c70963107bdd31134c4e249eef51133ceccf",
+ "https://deno.land/std@0.221.0/path/_common/glob_to_reg_exp.ts": "6cac16d5c2dc23af7d66348a7ce430e5de4e70b0eede074bdbcf4903f4374d8d",
+ "https://deno.land/std@0.221.0/path/_common/normalize.ts": "684df4aa71a04bbcc346c692c8485594fc8a90b9408dfbc26ff32cf3e0c98cc8",
+ "https://deno.land/std@0.221.0/path/_common/normalize_string.ts": "33edef773c2a8e242761f731adeb2bd6d683e9c69e4e3d0092985bede74f4ac3",
+ "https://deno.land/std@0.221.0/path/_common/relative.ts": "faa2753d9b32320ed4ada0733261e3357c186e5705678d9dd08b97527deae607",
+ "https://deno.land/std@0.221.0/path/_common/strip_trailing_separators.ts": "7024a93447efcdcfeaa9339a98fa63ef9d53de363f1fbe9858970f1bba02655a",
+ "https://deno.land/std@0.221.0/path/_common/to_file_url.ts": "7f76adbc83ece1bba173e6e98a27c647712cab773d3f8cbe0398b74afc817883",
+ "https://deno.land/std@0.221.0/path/_interface.ts": "8dfeb930ca4a772c458a8c7bbe1e33216fe91c253411338ad80c5b6fa93ddba0",
+ "https://deno.land/std@0.221.0/path/_os.ts": "8fb9b90fb6b753bd8c77cfd8a33c2ff6c5f5bc185f50de8ca4ac6a05710b2c15",
+ "https://deno.land/std@0.221.0/path/basename.ts": "7ee495c2d1ee516ffff48fb9a93267ba928b5a3486b550be73071bc14f8cc63e",
+ "https://deno.land/std@0.221.0/path/common.ts": "03e52e22882402c986fe97ca3b5bb4263c2aa811c515ce84584b23bac4cc2643",
+ "https://deno.land/std@0.221.0/path/constants.ts": "0c206169ca104938ede9da48ac952de288f23343304a1c3cb6ec7625e7325f36",
+ "https://deno.land/std@0.221.0/path/dirname.ts": "85bd955bf31d62c9aafdd7ff561c4b5fb587d11a9a5a45e2b01aedffa4238a7c",
+ "https://deno.land/std@0.221.0/path/extname.ts": "593303db8ae8c865cbd9ceec6e55d4b9ac5410c1e276bfd3131916591b954441",
+ "https://deno.land/std@0.221.0/path/format.ts": "6ce1779b0980296cf2bc20d66436b12792102b831fd281ab9eb08fa8a3e6f6ac",
+ "https://deno.land/std@0.221.0/path/from_file_url.ts": "911833ae4fd10a1c84f6271f36151ab785955849117dc48c6e43b929504ee069",
+ "https://deno.land/std@0.221.0/path/glob_to_regexp.ts": "7f30f0a21439cadfdae1be1bf370880b415e676097fda584a63ce319053b5972",
+ "https://deno.land/std@0.221.0/path/is_absolute.ts": "4791afc8bfd0c87f0526eaa616b0d16e7b3ab6a65b62942e50eac68de4ef67d7",
+ "https://deno.land/std@0.221.0/path/is_glob.ts": "a65f6195d3058c3050ab905705891b412ff942a292bcbaa1a807a74439a14141",
+ "https://deno.land/std@0.221.0/path/join.ts": "ae2ec5ca44c7e84a235fd532e4a0116bfb1f2368b394db1c4fb75e3c0f26a33a",
+ "https://deno.land/std@0.221.0/path/join_globs.ts": "5b3bf248b93247194f94fa6947b612ab9d3abd571ca8386cf7789038545e54a0",
+ "https://deno.land/std@0.221.0/path/mod.ts": "2821a1bb3a4148a0ffe79c92aa41aa9319fef73c6d6f5178f52b2c720d3eb02d",
+ "https://deno.land/std@0.221.0/path/normalize.ts": "4155743ccceeed319b350c1e62e931600272fad8ad00c417b91df093867a8352",
+ "https://deno.land/std@0.221.0/path/normalize_glob.ts": "cc89a77a7d3b1d01053b9dcd59462b75482b11e9068ae6c754b5cf5d794b374f",
+ "https://deno.land/std@0.221.0/path/parse.ts": "3e172974e3c71025f5fbd2bd9db4307acb9cc2de14cf6f4464bf40957663cabe",
+ "https://deno.land/std@0.221.0/path/posix/_util.ts": "1e3937da30f080bfc99fe45d7ed23c47dd8585c5e473b2d771380d3a6937cf9d",
+ "https://deno.land/std@0.221.0/path/posix/basename.ts": "d2fa5fbbb1c5a3ab8b9326458a8d4ceac77580961b3739cd5bfd1d3541a3e5f0",
+ "https://deno.land/std@0.221.0/path/posix/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4",
+ "https://deno.land/std@0.221.0/path/posix/constants.ts": "93481efb98cdffa4c719c22a0182b994e5a6aed3047e1962f6c2c75b7592bef1",
+ "https://deno.land/std@0.221.0/path/posix/dirname.ts": "76cd348ffe92345711409f88d4d8561d8645353ac215c8e9c80140069bf42f00",
+ "https://deno.land/std@0.221.0/path/posix/extname.ts": "e398c1d9d1908d3756a7ed94199fcd169e79466dd88feffd2f47ce0abf9d61d2",
+ "https://deno.land/std@0.221.0/path/posix/format.ts": "185e9ee2091a42dd39e2a3b8e4925370ee8407572cee1ae52838aed96310c5c1",
+ "https://deno.land/std@0.221.0/path/posix/from_file_url.ts": "951aee3a2c46fd0ed488899d024c6352b59154c70552e90885ed0c2ab699bc40",
+ "https://deno.land/std@0.221.0/path/posix/glob_to_regexp.ts": "76f012fcdb22c04b633f536c0b9644d100861bea36e9da56a94b9c589a742e8f",
+ "https://deno.land/std@0.221.0/path/posix/is_absolute.ts": "cebe561ad0ae294f0ce0365a1879dcfca8abd872821519b4fcc8d8967f888ede",
+ "https://deno.land/std@0.221.0/path/posix/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9",
+ "https://deno.land/std@0.221.0/path/posix/join.ts": "7fc2cb3716aa1b863e990baf30b101d768db479e70b7313b4866a088db016f63",
+ "https://deno.land/std@0.221.0/path/posix/join_globs.ts": "a9475b44645feddceb484ee0498e456f4add112e181cb94042cdc6d47d1cdd25",
+ "https://deno.land/std@0.221.0/path/posix/mod.ts": "2301fc1c54a28b349e20656f68a85f75befa0ee9b6cd75bfac3da5aca9c3f604",
+ "https://deno.land/std@0.221.0/path/posix/normalize.ts": "baeb49816a8299f90a0237d214cef46f00ba3e95c0d2ceb74205a6a584b58a91",
+ "https://deno.land/std@0.221.0/path/posix/normalize_glob.ts": "9c87a829b6c0f445d03b3ecadc14492e2864c3ebb966f4cea41e98326e4435c6",
+ "https://deno.land/std@0.221.0/path/posix/parse.ts": "0b1fc4cb890dbb699ec1d2c232d274843b4a7142e1ad976b69fe51c954eb6080",
+ "https://deno.land/std@0.221.0/path/posix/relative.ts": "3907d6eda41f0ff723d336125a1ad4349112cd4d48f693859980314d5b9da31c",
+ "https://deno.land/std@0.221.0/path/posix/resolve.ts": "08b699cfeee10cb6857ccab38fa4b2ec703b0ea33e8e69964f29d02a2d5257cf",
+ "https://deno.land/std@0.221.0/path/posix/to_file_url.ts": "7aa752ba66a35049e0e4a4be5a0a31ac6b645257d2e031142abb1854de250aaf",
+ "https://deno.land/std@0.221.0/path/posix/to_namespaced_path.ts": "28b216b3c76f892a4dca9734ff1cc0045d135532bfd9c435ae4858bfa5a2ebf0",
+ "https://deno.land/std@0.221.0/path/relative.ts": "ab739d727180ed8727e34ed71d976912461d98e2b76de3d3de834c1066667add",
+ "https://deno.land/std@0.221.0/path/resolve.ts": "a6f977bdb4272e79d8d0ed4333e3d71367cc3926acf15ac271f1d059c8494d8d",
+ "https://deno.land/std@0.221.0/path/to_file_url.ts": "88f049b769bce411e2d2db5bd9e6fd9a185a5fbd6b9f5ad8f52bef517c4ece1b",
+ "https://deno.land/std@0.221.0/path/to_namespaced_path.ts": "b706a4103b104cfadc09600a5f838c2ba94dbcdb642344557122dda444526e40",
+ "https://deno.land/std@0.221.0/path/windows/_util.ts": "d5f47363e5293fced22c984550d5e70e98e266cc3f31769e1710511803d04808",
+ "https://deno.land/std@0.221.0/path/windows/basename.ts": "6bbc57bac9df2cec43288c8c5334919418d784243a00bc10de67d392ab36d660",
+ "https://deno.land/std@0.221.0/path/windows/common.ts": "26f60ccc8b2cac3e1613000c23ac5a7d392715d479e5be413473a37903a2b5d4",
+ "https://deno.land/std@0.221.0/path/windows/constants.ts": "5afaac0a1f67b68b0a380a4ef391bf59feb55856aa8c60dfc01bd3b6abb813f5",
+ "https://deno.land/std@0.221.0/path/windows/dirname.ts": "33e421be5a5558a1346a48e74c330b8e560be7424ed7684ea03c12c21b627bc9",
+ "https://deno.land/std@0.221.0/path/windows/extname.ts": "165a61b00d781257fda1e9606a48c78b06815385e7d703232548dbfc95346bef",
+ "https://deno.land/std@0.221.0/path/windows/format.ts": "bbb5ecf379305b472b1082cd2fdc010e44a0020030414974d6029be9ad52aeb6",
+ "https://deno.land/std@0.221.0/path/windows/from_file_url.ts": "ced2d587b6dff18f963f269d745c4a599cf82b0c4007356bd957cb4cb52efc01",
+ "https://deno.land/std@0.221.0/path/windows/glob_to_regexp.ts": "e45f1f89bf3fc36f94ab7b3b9d0026729829fabc486c77f414caebef3b7304f8",
+ "https://deno.land/std@0.221.0/path/windows/is_absolute.ts": "4a8f6853f8598cf91a835f41abed42112cebab09478b072e4beb00ec81f8ca8a",
+ "https://deno.land/std@0.221.0/path/windows/is_glob.ts": "8a8b08c08bf731acf2c1232218f1f45a11131bc01de81e5f803450a5914434b9",
+ "https://deno.land/std@0.221.0/path/windows/join.ts": "8d03530ab89195185103b7da9dfc6327af13eabdcd44c7c63e42e27808f50ecf",
+ "https://deno.land/std@0.221.0/path/windows/join_globs.ts": "a9475b44645feddceb484ee0498e456f4add112e181cb94042cdc6d47d1cdd25",
+ "https://deno.land/std@0.221.0/path/windows/mod.ts": "2301fc1c54a28b349e20656f68a85f75befa0ee9b6cd75bfac3da5aca9c3f604",
+ "https://deno.land/std@0.221.0/path/windows/normalize.ts": "78126170ab917f0ca355a9af9e65ad6bfa5be14d574c5fb09bb1920f52577780",
+ "https://deno.land/std@0.221.0/path/windows/normalize_glob.ts": "9c87a829b6c0f445d03b3ecadc14492e2864c3ebb966f4cea41e98326e4435c6",
+ "https://deno.land/std@0.221.0/path/windows/parse.ts": "dbdfe2bc6db482d755b5f63f7207cd019240fcac02ad2efa582adf67ff10553a",
+ "https://deno.land/std@0.221.0/path/windows/relative.ts": "3e1abc7977ee6cc0db2730d1f9cb38be87b0ce4806759d271a70e4997fc638d7",
+ "https://deno.land/std@0.221.0/path/windows/resolve.ts": "8dae1dadfed9d46ff46cc337c9525c0c7d959fb400a6308f34595c45bdca1972",
+ "https://deno.land/std@0.221.0/path/windows/to_file_url.ts": "40e560ee4854fe5a3d4d12976cef2f4e8914125c81b11f1108e127934ced502e",
+ "https://deno.land/std@0.221.0/path/windows/to_namespaced_path.ts": "4ffa4fb6fae321448d5fe810b3ca741d84df4d7897e61ee29be961a6aac89a4c",
+ "https://deno.land/std@0.81.0/_util/assert.ts": "e1f76e77c5ccb5a8e0dbbbe6cce3a56d2556c8cb5a9a8802fc9565af72462149",
+ "https://deno.land/std@0.81.0/bytes/mod.ts": "e4f91c6473fe13e3cf1a23649137f87f49135c10bc08fc0f83382a0fb0b03744",
+ "https://deno.land/std@0.81.0/encoding/utf8.ts": "1b7e77db9a12363c67872f8a208886ca1329f160c1ca9133b13d2ed399688b99",
+ "https://deno.land/std@0.81.0/io/bufio.ts": "3cbbe1f761c1c636d1e7128ed4e7fdca6bf21d9199aa3cae71e69972a6ae8f93",
+ "https://deno.land/std@0.81.0/textproto/mod.ts": "4c378eda3cb6216608bb4c3a34201761c65f6980c4669455ca224c330cd5b790",
+ "https://deno.land/x/bcrypt@v0.4.1/mod.ts": "ff09bdae282583cf5f7d87efe37ddcecef7f14f6d12e8b8066a3058db8c6c2f7",
+ "https://deno.land/x/bcrypt@v0.4.1/src/bcrypt/base64.ts": "b8266450a4f1eb6960f60f2f7986afc4dde6b45bd2d7ee7ba10789e67e17b9f7",
+ "https://deno.land/x/bcrypt@v0.4.1/src/bcrypt/bcrypt.ts": "ec221648cc6453ea5e3803bc817c01157dada06aa6f7a0ba6b9f87aae32b21e2",
+ "https://deno.land/x/bcrypt@v0.4.1/src/main.ts": "08d201b289c8d9c46f8839c69cd6625b213863db29775c7a200afc3b540e64f8",
+ "https://deno.land/x/bcrypt@v0.4.1/src/worker.ts": "5a73bdfee9c9e622f47c9733d374b627dce52fb3ec1e74c8226698b3fc57ffac",
+ "https://deno.land/x/denomailer@1.6.0/client/basic/QUE.ts": "5af1dfcc5814bf4542f098908ac1fdd8a1a1c2b1597138a121c95eaa791315d0",
+ "https://deno.land/x/denomailer@1.6.0/client/basic/client.ts": "462e4db45ae218647812ceae720d55ea33e0e928f9138fee9da5913cbb1e20f9",
+ "https://deno.land/x/denomailer@1.6.0/client/basic/connection.ts": "68de68d7551d8303629905c2b7581cb09b45646e530ce93a5786ca1aba61055c",
+ "https://deno.land/x/denomailer@1.6.0/client/basic/transforms.ts": "e630a23d24e9b397e231ae8796c0a0080770ac6f5ab9bffc105d3717706e62c9",
+ "https://deno.land/x/denomailer@1.6.0/client/mod.ts": "8ec4c25d9586f83f8629768311a077eaf03b1490dcb872030ba2e27fadb674d8",
+ "https://deno.land/x/denomailer@1.6.0/client/pool.ts": "0466e69ca8959aa85501cc6b30d7f5fd8e43b0a6ac88ecc60dab71081e801bae",
+ "https://deno.land/x/denomailer@1.6.0/client/worker/worker.ts": "a4c3a3e2e1fde0967817ece7c345a565eb44a7312acf8d46ce620d4ff4443b31",
+ "https://deno.land/x/denomailer@1.6.0/config/client.ts": "302f5c18fbb5531b5615613084b86d44120acc210e072f4135e431fa27fc4526",
+ "https://deno.land/x/denomailer@1.6.0/config/mail/attachments.ts": "1f357bddc9d5e813c3f647498db81a165a1a8a7163116c58dc10cf01427fd81e",
+ "https://deno.land/x/denomailer@1.6.0/config/mail/content.ts": "3925d4c3baaabed4e08933159d34b1450e6426b35a5bc323a0666780bef20192",
+ "https://deno.land/x/denomailer@1.6.0/config/mail/email.ts": "bb0ca104bf9cb54af6613a04b3f8cb05290f4fb012e7113f1d050cab10226a7c",
+ "https://deno.land/x/denomailer@1.6.0/config/mail/encoding.ts": "0bc5983ada3b902333925cdca225f8ea5e28fffbc2f1bd2b0ccb9a423f6f7fcc",
+ "https://deno.land/x/denomailer@1.6.0/config/mail/headers.ts": "ce94874beb5a1a7248b5b91bf1ae3b3aed2d4c0541f3f448f2bbfad6c8f570ee",
+ "https://deno.land/x/denomailer@1.6.0/config/mail/mod.ts": "a7fafa3386a45a585d7983d816b09ddc28b2f2b84097a614f1e38685b1f62868",
+ "https://deno.land/x/denomailer@1.6.0/deps.ts": "12bef188bb2a490fedc82ac1889f3d438e8a15887c423b045fee532b31a43102",
+ "https://deno.land/x/denomailer@1.6.0/mod.ts": "71a197dff098194ab53691abd3c9d22a276ef04e1382eb85f5632dbcb5a83bf3",
+ "https://deno.land/x/loadenv@v1.0.1/mod.ts": "287c1cc1b4bf024a1381610428d783854853aa36cca6f927e5f6dba81f350616",
+ "https://deno.land/x/loadenv@v1.0.1/src/main.ts": "216f20535f9e41be15679b0a48bba5eeb8dcf12b3ad1a1ab1971c5b0ae698e0a",
+ "https://deno.land/x/smtp@v0.7.0/code.ts": "f388fae4995b4d35d99fb6b8bfded522f5a3e7e7d63babdf318a059d6db43baf",
+ "https://deno.land/x/smtp@v0.7.0/deps.ts": "5e2a437e3ae35f0e83719fd2e707858dcb750c1111ff5bebc729522a1380b53d",
+ "https://deno.land/x/smtp@v0.7.0/mod.ts": "9b0d8fbdacc184d1af10f727980e51486e0ddf9d2ec7227c8dfce90db5bfbcf5",
+ "https://deno.land/x/smtp@v0.7.0/smtp.ts": "47c72a99925ad07f3174037f9325dbb8b703dc1177277b9161dc6209c7fa4f90"
+ },
+ "workspace": {
+ "dependencies": [
+ "npm:yaml@2.4.2"
+ ]
+ }
+}
diff --git a/app/server/mod.ts b/app/server/mod.ts
new file mode 100644
index 0000000..fe6999f
--- /dev/null
+++ b/app/server/mod.ts
@@ -0,0 +1,11 @@
+import { System } from "./src/main.ts";
+import { load as loadEnv } from "loadenv";
+import { getProcessedEnvs } from "shared/utils/main.ts";
+
+const envs = getProcessedEnvs({
+ version: "__VERSION__",
+ isDevelopment: false,
+});
+
+loadEnv();
+await System.load(envs);
diff --git a/app/server/src/main.ts b/app/server/src/main.ts
new file mode 100644
index 0000000..ec02433
--- /dev/null
+++ b/app/server/src/main.ts
@@ -0,0 +1 @@
+export * from "./system/main.ts";
diff --git a/app/server/src/modules/api/main.ts b/app/server/src/modules/api/main.ts
new file mode 100644
index 0000000..ee92120
--- /dev/null
+++ b/app/server/src/modules/api/main.ts
@@ -0,0 +1,9 @@
+import { RequestType } from "shared/types/request.types.ts";
+import { getPathRequestList } from "shared/utils/main.ts";
+
+import { versionRequest } from "./version.request.ts";
+
+export const requestList: RequestType[] = getPathRequestList({
+ requestList: [versionRequest],
+ pathname: "/api",
+});
diff --git a/app/server/src/modules/api/version.http b/app/server/src/modules/api/version.http
new file mode 100644
index 0000000..11a3921
--- /dev/null
+++ b/app/server/src/modules/api/version.http
@@ -0,0 +1,2 @@
+# Version
+GET http://localhost:2024/api/v2/version
diff --git a/app/server/src/modules/api/version.request.ts b/app/server/src/modules/api/version.request.ts
new file mode 100644
index 0000000..b4bed8e
--- /dev/null
+++ b/app/server/src/modules/api/version.request.ts
@@ -0,0 +1,10 @@
+import { RequestType } from "shared/types/main.ts";
+import { RequestMethod } from "shared/enums/main.ts";
+
+export const versionRequest: RequestType = {
+ method: RequestMethod.GET,
+ pathname: "/version",
+ func: (request, url) => {
+ return Response.json({}, { status: 200 });
+ },
+};
diff --git a/app/server/src/modules/updater/main.ts b/app/server/src/modules/updater/main.ts
new file mode 100644
index 0000000..7651fbc
--- /dev/null
+++ b/app/server/src/modules/updater/main.ts
@@ -0,0 +1,117 @@
+import {
+ getOS,
+ getOSName,
+ getPath,
+ getTemporalUpdateFilePathname,
+} from "shared/utils/main.ts";
+import { OS } from "shared/enums/main.ts";
+import * as path from "deno/path/mod.ts";
+import { isDevelopment } from "shared/utils/environment.utils.ts";
+import { Envs } from "shared/types/envs.types.ts";
+
+export const load = async (envs: Envs): Promise => {
+ if (isDevelopment()) return false;
+
+ const os = getOS();
+ const osName = getOSName() as string;
+
+ console.log(`OS ${osName}`);
+
+ if (os === OS.UNKNOWN) {
+ console.log(`Unknown OS (${Deno.build.os}) cannot be updated!`);
+ return false;
+ }
+
+ console.log(`Version ${envs.version}`);
+ console.log(`Checking for updates...`);
+
+ try {
+ const { tag_name: latestVersion, assets } = await fetch(
+ "https://api.github.com/repos/openhotel/asset-editor/releases/latest",
+ ).then((data) => data.json());
+
+ const getSlicedVersion = (version: string): (number | string)[] =>
+ version
+ .slice(1)
+ .split(".")
+ .map((e: string) => {
+ const num = parseInt(e);
+ return `${num}` === e ? num : e;
+ });
+
+ const [oldMajor, oldMinor, oldPatch, oldExtra] = getSlicedVersion(
+ envs.version,
+ );
+ const [newMajor, newMinor, newPatch, newExtra] =
+ getSlicedVersion(latestVersion);
+
+ if (
+ oldMajor >= newMajor &&
+ oldMinor >= newMinor &&
+ oldPatch >= newPatch &&
+ (oldExtra >= newExtra || oldExtra === newExtra)
+ ) {
+ console.log("Everything is up to date!");
+ return false;
+ }
+ console.log(`New version (${latestVersion}) available!`);
+
+ const osAsset = assets.find(({ name }: { name: string }) =>
+ name.includes(osName),
+ );
+
+ if (!osAsset) {
+ console.log(`No file found to update on (${osName})!`);
+ return false;
+ }
+
+ console.log("Downloading update...");
+ const buildAsset = await fetch(osAsset.browser_download_url);
+
+ console.log("Update downloaded!");
+ const dirPath = getPath();
+ const updateFilePath = getTemporalUpdateFilePathname();
+ const currentFile = path.join(dirPath, `assets_editor_${osName}`);
+ const updatedFile = path.join(dirPath, `update__assets_editor_${osName}`);
+
+ console.log("Saving update files!");
+ await Deno.writeFile(
+ updatedFile,
+ new Uint8Array(await buildAsset.arrayBuffer()),
+ {
+ mode: 0x777,
+ },
+ );
+ await Deno.chmod(updatedFile, 0o777);
+
+ try {
+ await Deno.remove(updateFilePath);
+ } catch (e) {}
+
+ const bash = `#! /bin/bash
+ rm ${currentFile}
+ mv '${updatedFile}' '${currentFile}'
+ `;
+
+ console.log("Updating...");
+ await Deno.writeTextFile(updateFilePath, bash, {
+ mode: 0x0777,
+ create: true,
+ });
+
+ const updater = Deno.run({
+ cmd: ["sh", updateFilePath],
+ stdin: "null",
+ stdout: "null",
+ stderr: "null",
+ detached: true,
+ });
+ await updater.status();
+ console.log("Restart to apply the update!");
+ return true;
+ } catch (e) {
+ console.debug(e);
+ console.log("Something went wrong checking for update.");
+ }
+ return false;
+};
diff --git a/app/server/src/shared/consts/config.consts.ts b/app/server/src/shared/consts/config.consts.ts
new file mode 100644
index 0000000..5c036a6
--- /dev/null
+++ b/app/server/src/shared/consts/config.consts.ts
@@ -0,0 +1,6 @@
+import { ConfigTypes } from "shared/types/config.types.ts";
+
+export const CONFIG_DEFAULT: ConfigTypes = {
+ port: 2030,
+ url: "http://localhost:2030",
+};
diff --git a/app/server/src/shared/consts/main.ts b/app/server/src/shared/consts/main.ts
new file mode 100644
index 0000000..fd33bfe
--- /dev/null
+++ b/app/server/src/shared/consts/main.ts
@@ -0,0 +1,4 @@
+export * from "./config.consts.ts";
+export * from "./session.consts.ts";
+export * from "./account.consts.ts";
+export * from "./tickets.consts.ts";
diff --git a/app/server/src/shared/enums/main.ts b/app/server/src/shared/enums/main.ts
new file mode 100644
index 0000000..44dc1d7
--- /dev/null
+++ b/app/server/src/shared/enums/main.ts
@@ -0,0 +1,2 @@
+export * from "./request.enum.ts";
+export * from "./os.enum.ts";
diff --git a/app/server/src/shared/enums/os.enum.ts b/app/server/src/shared/enums/os.enum.ts
new file mode 100644
index 0000000..ed1db57
--- /dev/null
+++ b/app/server/src/shared/enums/os.enum.ts
@@ -0,0 +1,5 @@
+export enum OS {
+ LINUX,
+ DARWIN,
+ UNKNOWN,
+}
diff --git a/app/server/src/shared/enums/request.enum.ts b/app/server/src/shared/enums/request.enum.ts
new file mode 100644
index 0000000..f7bf24e
--- /dev/null
+++ b/app/server/src/shared/enums/request.enum.ts
@@ -0,0 +1,6 @@
+export enum RequestMethod {
+ GET = "GET",
+ POST = "POST",
+ PUT = "PUT",
+ DELETE = "DELETE",
+}
diff --git a/app/server/src/shared/types/config.types.ts b/app/server/src/shared/types/config.types.ts
new file mode 100644
index 0000000..ad0774b
--- /dev/null
+++ b/app/server/src/shared/types/config.types.ts
@@ -0,0 +1,4 @@
+export type ConfigTypes = {
+ port: number;
+ url: string;
+};
diff --git a/app/server/src/shared/types/envs.types.ts b/app/server/src/shared/types/envs.types.ts
new file mode 100644
index 0000000..dcc565e
--- /dev/null
+++ b/app/server/src/shared/types/envs.types.ts
@@ -0,0 +1,4 @@
+export type Envs = {
+ version: string;
+ isDevelopment: boolean;
+};
diff --git a/app/server/src/shared/types/main.ts b/app/server/src/shared/types/main.ts
new file mode 100644
index 0000000..71a3b9c
--- /dev/null
+++ b/app/server/src/shared/types/main.ts
@@ -0,0 +1,4 @@
+export * from "./request.types.ts";
+export * from "./config.types.ts";
+export * from "./yaml.types.ts";
+export * from "./envs.types.ts";
diff --git a/app/server/src/shared/types/request.types.ts b/app/server/src/shared/types/request.types.ts
new file mode 100644
index 0000000..379a935
--- /dev/null
+++ b/app/server/src/shared/types/request.types.ts
@@ -0,0 +1,7 @@
+import { RequestMethod } from "../enums/main.ts";
+
+export type RequestType = {
+ method: RequestMethod;
+ pathname: string;
+ func: (request: Request, url: URL) => Response | Promise;
+};
diff --git a/app/server/src/shared/types/yaml.types.ts b/app/server/src/shared/types/yaml.types.ts
new file mode 100644
index 0000000..0e0860c
--- /dev/null
+++ b/app/server/src/shared/types/yaml.types.ts
@@ -0,0 +1,8 @@
+export type ReadProps = {
+ decode?: boolean;
+};
+
+export type WriteProps = {
+ encode?: boolean;
+ async?: boolean;
+};
diff --git a/app/server/src/shared/utils/config.utils.ts b/app/server/src/shared/utils/config.utils.ts
new file mode 100644
index 0000000..f732269
--- /dev/null
+++ b/app/server/src/shared/utils/config.utils.ts
@@ -0,0 +1,20 @@
+import { readYaml, writeYaml } from "./yaml.utils.ts";
+import { ConfigTypes } from "shared/types/config.types.ts";
+import { CONFIG_DEFAULT } from "shared/consts/config.consts.ts";
+
+export const getConfig = async (): Promise => {
+ let config: ConfigTypes = {} as ConfigTypes;
+ try {
+ config = await readYaml("./config.yml");
+ } catch (e) {}
+
+ const defaults: ConfigTypes = {
+ port: config?.port || CONFIG_DEFAULT.port,
+ url: config?.url || CONFIG_DEFAULT.url,
+ };
+ try {
+ await writeYaml("./config.yml", defaults, { async: true });
+ } catch (e) {}
+
+ return defaults;
+};
diff --git a/app/server/src/shared/utils/content-type.utils.ts b/app/server/src/shared/utils/content-type.utils.ts
new file mode 100644
index 0000000..0a146ac
--- /dev/null
+++ b/app/server/src/shared/utils/content-type.utils.ts
@@ -0,0 +1,15 @@
+export const getContentType = (targetFile: string): string => {
+ let contentType = "text/plain";
+ if (targetFile.endsWith(".js")) {
+ contentType = "application/javascript";
+ } else if (targetFile.endsWith(".css")) {
+ contentType = "text/css";
+ } else if (targetFile.endsWith(".html")) {
+ contentType = "text/html";
+ } else if (targetFile.endsWith(".json")) {
+ contentType = "application/json";
+ } else if (targetFile.endsWith(".png")) {
+ contentType = "image/png";
+ }
+ return contentType;
+};
diff --git a/app/server/src/shared/utils/cors.utils.ts b/app/server/src/shared/utils/cors.utils.ts
new file mode 100644
index 0000000..eb5493a
--- /dev/null
+++ b/app/server/src/shared/utils/cors.utils.ts
@@ -0,0 +1,19 @@
+const HEADERS = [
+ ["Access-Control-Allow-Origin", "*"],
+ ["Access-Control-Allow-Methods", "GET, POST, PUT", "DELETE"],
+ [
+ "Access-Control-Allow-Headers",
+ "Origin, X-Requested-With, Content-Type, Accept",
+ ],
+ ["Access-Control-Allow-Credentials", "true"],
+];
+
+export const getCORSHeaders = (): Headers => {
+ const headers = new Headers();
+ for (const [key, value] of HEADERS) headers.append(key, value);
+ return headers;
+};
+1;
+export const appendCORSHeaders = (headers: Headers) => {
+ for (const [key, value] of HEADERS) headers.append(key, value);
+};
diff --git a/app/server/src/shared/utils/directory.utils.ts b/app/server/src/shared/utils/directory.utils.ts
new file mode 100644
index 0000000..37ab54c
--- /dev/null
+++ b/app/server/src/shared/utils/directory.utils.ts
@@ -0,0 +1,7 @@
+import * as path from "deno/path/mod.ts";
+import { exists } from "deno/fs/mod.ts";
+
+export const createDirectoryIfNotExists = async (filePath: string) => {
+ const dirPath = path.dirname(filePath);
+ if (!(await exists(dirPath))) await Deno.mkdir(dirPath, { recursive: true });
+};
diff --git a/app/server/src/shared/utils/environment.utils.ts b/app/server/src/shared/utils/environment.utils.ts
new file mode 100644
index 0000000..2d22ed0
--- /dev/null
+++ b/app/server/src/shared/utils/environment.utils.ts
@@ -0,0 +1,8 @@
+import { Envs } from "shared/types/main.ts";
+
+export const isDevelopment = () => Deno.env.get("ENV") === "development";
+
+export const getProcessedEnvs = ({ version }: Envs): Envs => ({
+ version: version === "__VERSION__" ? "DEVELOPMENT" : version,
+ isDevelopment: version === "__VERSION__",
+});
diff --git a/app/server/src/shared/utils/main.ts b/app/server/src/shared/utils/main.ts
new file mode 100644
index 0000000..4bdc907
--- /dev/null
+++ b/app/server/src/shared/utils/main.ts
@@ -0,0 +1,11 @@
+export * from "./request.utils.ts";
+export * from "./cors.utils.ts";
+export * from "./random.utils.ts";
+export * from "./config.utils.ts";
+export * from "./directory.utils.ts";
+export * from "./yaml.utils.ts";
+export * from "./path.utils.ts";
+export * from "./content-type.utils.ts";
+export * from "./update.utils.ts";
+export * from "./os.utils.ts";
+export * from "./environment.utils.ts";
diff --git a/app/server/src/shared/utils/os.utils.ts b/app/server/src/shared/utils/os.utils.ts
new file mode 100644
index 0000000..89bfb70
--- /dev/null
+++ b/app/server/src/shared/utils/os.utils.ts
@@ -0,0 +1,21 @@
+import { OS } from "shared/enums/main.ts";
+
+export const getOS = () => {
+ switch (Deno.build.os) {
+ case "linux":
+ return OS.LINUX;
+ case "darwin":
+ return OS.DARWIN;
+ }
+ return OS.UNKNOWN;
+};
+
+export const getOSName = () => {
+ const { os } = Deno.build;
+ switch (os) {
+ case "linux":
+ case "darwin":
+ return os;
+ }
+ return undefined;
+};
diff --git a/app/server/src/shared/utils/path.utils.ts b/app/server/src/shared/utils/path.utils.ts
new file mode 100644
index 0000000..f0c0837
--- /dev/null
+++ b/app/server/src/shared/utils/path.utils.ts
@@ -0,0 +1,4 @@
+import * as path from "deno/path/mod.ts";
+
+export const getExecPath = () => Deno.execPath();
+export const getPath = () => path.dirname(getExecPath());
diff --git a/app/server/src/shared/utils/random.utils.ts b/app/server/src/shared/utils/random.utils.ts
new file mode 100644
index 0000000..fe0b64a
--- /dev/null
+++ b/app/server/src/shared/utils/random.utils.ts
@@ -0,0 +1,15 @@
+export const getRandomNumber = (min: number, max: number): number =>
+ Math.round(Math.random() * (max - min)) + min;
+
+export const getRandomString = (length: number) => {
+ let result = "";
+ const characters =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ const charactersLength = characters.length;
+ let counter = 0;
+ while (counter < length) {
+ result += characters.charAt(Math.floor(Math.random() * charactersLength));
+ counter += 1;
+ }
+ return result;
+};
diff --git a/app/server/src/shared/utils/request.utils.ts b/app/server/src/shared/utils/request.utils.ts
new file mode 100644
index 0000000..02ace42
--- /dev/null
+++ b/app/server/src/shared/utils/request.utils.ts
@@ -0,0 +1,13 @@
+import { RequestType } from "../types/main.ts";
+
+export const getPathRequestList = ({
+ requestList,
+ pathname,
+}: {
+ requestList: RequestType[];
+ pathname: string;
+}): RequestType[] =>
+ requestList.map((request) => ({
+ ...request,
+ pathname: `${pathname}${request.pathname}`,
+ }));
diff --git a/app/server/src/shared/utils/update.utils.ts b/app/server/src/shared/utils/update.utils.ts
new file mode 100644
index 0000000..b249278
--- /dev/null
+++ b/app/server/src/shared/utils/update.utils.ts
@@ -0,0 +1,8 @@
+import { getPath } from "./path.utils.ts";
+import * as path from "deno/path/mod.ts";
+
+export const getTemporalUpdateFilePathname = () => {
+ const dirPath = getPath();
+
+ return path.join(dirPath, "./updater.sh");
+};
diff --git a/app/server/src/shared/utils/yaml.utils.ts b/app/server/src/shared/utils/yaml.utils.ts
new file mode 100644
index 0000000..e69d6e9
--- /dev/null
+++ b/app/server/src/shared/utils/yaml.utils.ts
@@ -0,0 +1,34 @@
+import { ReadProps, WriteProps } from "shared/types/yaml.types.ts";
+import yamlLibrary from "yaml";
+import { createDirectoryIfNotExists } from "./directory.utils.ts";
+
+const encoder = new TextEncoder();
+const decoder = new TextDecoder();
+
+export const readYaml = async (
+ filePath: string,
+ { decode = false }: ReadProps = { decode: false },
+): Promise => {
+ let content = await Deno.readTextFile(filePath);
+ if (decode)
+ content = decoder.decode(new Uint8Array(content.split(",").map(Number)));
+ return yamlLibrary.parse(content);
+};
+
+export const writeYaml = async (
+ filePath: string,
+ data: T,
+ { encode = false, async = false }: WriteProps = {
+ encode: false,
+ async: false,
+ },
+) => {
+ await createDirectoryIfNotExists(filePath);
+
+ let content = yamlLibrary.stringify(data);
+ if (encode) content = encoder.encode(content);
+
+ const write = () => Deno.writeTextFile(filePath, content);
+
+ async ? await write() : write();
+};
diff --git a/app/server/src/system/api.ts b/app/server/src/system/api.ts
new file mode 100644
index 0000000..118a0bd
--- /dev/null
+++ b/app/server/src/system/api.ts
@@ -0,0 +1,58 @@
+import { requestList } from "modules/api/main.ts";
+import { appendCORSHeaders } from "shared/utils/main.ts";
+import { System } from "system/main.ts";
+import { clientIndex } from "../../client-index.ts";
+import { isDevelopment } from "shared/utils/environment.utils.ts";
+
+export const api = () => {
+ const load = () => {
+ for (const request of requestList)
+ console.info(request.method, request.pathname);
+
+ Deno.serve(
+ {
+ port: System.getConfig().port * (isDevelopment() ? 10 : 1),
+ },
+ async (request: Request) => {
+ try {
+ const { url, method } = request;
+ const parsedUrl = new URL(url);
+
+ if (!parsedUrl.pathname.startsWith("/api")) {
+ return new Response(decodeURIComponent(clientIndex), {
+ headers: {
+ "Content-Type": "text/html",
+ },
+ });
+ }
+
+ const foundRequests = requestList.filter(
+ ($request) =>
+ // $request.method === method &&
+ $request.pathname === parsedUrl.pathname,
+ );
+ const foundMethodRequest = foundRequests.find(
+ ($request) => $request.method === method,
+ );
+ if (foundMethodRequest) {
+ const response = await foundMethodRequest.func(request, parsedUrl);
+ appendCORSHeaders(response.headers);
+ return response;
+ }
+ if (foundRequests.length)
+ return new Response("200", {
+ status: 200,
+ });
+ return new Response("404", { status: 404 });
+ } catch (e) {
+ console.log(e);
+ }
+ return new Response("500", { status: 500 });
+ },
+ );
+ };
+
+ return {
+ load,
+ };
+};
diff --git a/app/server/src/system/main.ts b/app/server/src/system/main.ts
new file mode 100644
index 0000000..6543f89
--- /dev/null
+++ b/app/server/src/system/main.ts
@@ -0,0 +1,27 @@
+import { api } from "./api.ts";
+import { ConfigTypes, Envs } from "shared/types/main.ts";
+import { getConfig as $getConfig } from "shared/utils/main.ts";
+import { load as loadUpdater } from "../modules/updater/main.ts";
+
+export const System = (() => {
+ const $api = api();
+
+ let $config: ConfigTypes;
+
+ const load = async (envs: Envs) => {
+ if (await loadUpdater(envs)) return;
+
+ $config = await $getConfig();
+
+ $api.load();
+ };
+
+ const getConfig = (): ConfigTypes => $config;
+
+ return {
+ load,
+ getConfig,
+
+ api: $api,
+ };
+})();
diff --git a/app/server/tsconfig.json b/app/server/tsconfig.json
new file mode 100644
index 0000000..ead3163
--- /dev/null
+++ b/app/server/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "moduleResolution": "node",
+ "module": "ES2022",
+ "target": "ES2022",
+ "esModuleInterop": true,
+ "sourceMap": false,
+ "noUnusedLocals": true,
+ "lib": ["ES2023", "dom"],
+ "baseUrl": "./src/",
+ "rootDir": "./src",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true
+ }
+}
diff --git a/deno.json b/deno.json
new file mode 100644
index 0000000..9bc82e5
--- /dev/null
+++ b/deno.json
@@ -0,0 +1,10 @@
+{
+ "tasks": {
+ "start": "deno task start:server & deno task start:client",
+ "start:nowatch": "deno task start:server:nowatch & deno task start:client",
+
+ "start:server": "cd ./app/server && deno task start",
+ "start:server:nowatch": "cd ./app/server && deno task start:nowatch",
+ "start:client": "cd ./app/client && yarn start"
+ }
+}
diff --git a/deno.lock b/deno.lock
new file mode 100644
index 0000000..80920b2
--- /dev/null
+++ b/deno.lock
@@ -0,0 +1,22 @@
+{
+ "version": "3",
+ "packages": {
+ "specifiers": {
+ "npm:prettier@3.3.2": "npm:prettier@3.3.2"
+ },
+ "npm": {
+ "prettier@3.3.2": {
+ "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==",
+ "dependencies": {}
+ }
+ }
+ },
+ "remote": {},
+ "workspace": {
+ "packageJson": {
+ "dependencies": [
+ "npm:prettier@3.3.2"
+ ]
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..17a4871
--- /dev/null
+++ b/package.json
@@ -0,0 +1,10 @@
+{
+ "scripts": {
+ "prettier:check": "prettier -c ./",
+ "prettier:write": "prettier --write ."
+ },
+ "devDependencies": {
+ "prettier": "3.3.2"
+ },
+ "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..11b2a0d
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "compilerOptions": {
+ "moduleResolution": "node",
+ "module": "ES2022",
+ "target": "ES2022",
+ "esModuleInterop": true,
+ "sourceMap": false,
+ "noUnusedLocals": true,
+ "lib": ["ES2022", "dom"],
+ "baseUrl": "./src/",
+ "rootDir": "./src"
+ }
+}
diff --git a/yarn.lock b/yarn.lock
new file mode 100644
index 0000000..6ca1204
--- /dev/null
+++ b/yarn.lock
@@ -0,0 +1,8 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+prettier@3.3.2:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.2.tgz#03ff86dc7c835f2d2559ee76876a3914cec4a90a"
+ integrity sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==