├── .env ├── .gitignore ├── README.md ├── assets ├── .DS_Store └── img │ ├── app-logo-mac.png │ ├── app-logo-win.ico │ ├── app-logo.svg │ ├── icon-black 2.png │ ├── icon-black.png │ ├── icon-user 2.png │ ├── icon-user.png │ └── icon-white.png ├── build.sql ├── license.md ├── package-lock.json ├── package.json ├── postcss.config.js ├── src ├── App.css ├── App.tsx ├── React.tsx ├── components │ ├── BackEndpoint.tsx │ ├── BackLog.tsx │ ├── BackRequestEditor.tsx │ ├── ContractEditor.tsx │ ├── ContractEndpoint.tsx │ ├── DocumentExport.tsx │ ├── DocumentHeading.tsx │ ├── DocumentPreview.tsx │ ├── FrontLog.tsx │ ├── FrontTestBanner.tsx │ ├── Login.tsx │ ├── ModalContractDetails.tsx │ ├── ModalJoinContract.tsx │ ├── ModalNewContract.tsx │ ├── Navbar.tsx │ ├── Notification.tsx │ ├── Register.tsx │ └── modalNotification.tsx ├── containers │ ├── BackTester.tsx │ ├── ContractBuilder.tsx │ ├── CounterContainer.tsx │ ├── DocumentCreator.tsx │ └── FrontTester.tsx ├── express │ ├── testing_server │ │ ├── controllers │ │ │ ├── checkController.js │ │ │ ├── contractOp.js │ │ │ ├── contractOp.test.js │ │ │ ├── dbController.js │ │ │ ├── mockController.js │ │ │ └── mockResExport.js │ │ ├── models │ │ │ └── dbModel.js │ │ └── testing_server.js │ └── user_info_server │ │ ├── controllers │ │ ├── commands.sql │ │ ├── dbController.js │ │ └── userController.js │ │ ├── models │ │ └── dbModel.js │ │ ├── routes │ │ ├── contract.js │ │ ├── login.js │ │ └── signup.js │ │ └── user_server.js ├── index.html ├── main.ts ├── preload.ts ├── renderer.ts ├── splash.css ├── splash.html └── state │ ├── features │ ├── backLogSlice.ts │ ├── contractSlice.ts │ ├── counterSlice.ts │ ├── frontLogSlice.ts │ ├── modalsSlice.ts │ └── userSlice.ts │ └── store.ts ├── tailwind.config.js ├── tsconfig.json └── webpack.config.js /.env: -------------------------------------------------------------------------------- 1 | DB_KEY=postgres://zzzenrbw:qKcrtsvOIBu_eBn7vP9l83Nk0V_ij_md@heffalump.db.elephantsql.com/zzzenrbw 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_store 3 | dist 4 | assets/.DS_Store 5 | installer 6 | # .env 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | # [**_CONTRACTUAL_**](https://www.contractualapp.io/) 5 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)]() 6 | ![Release: 7.0.1](https://img.shields.io/badge/Release-1.0.0-red) 7 | ![License: MIT](https://img.shields.io/badge/License-MIT-orange.svg) 8 | ![Contributions Welcome](https://img.shields.io/badge/Contributions-welcome-blue.svg) 9 | [![Github stars](https://img.shields.io/github/stars/oslabs-beta/contractual?style=social)]() 10 | ![image](https://user-images.githubusercontent.com/43141076/165818499-84d305cb-bf94-46f2-8dc8-f46fa37670ef.png) 11 | 12 | 13 |
14 | 15 | ## Table of Contents 16 | 17 | 18 | 19 | - [0. Brief](#0-brief) 20 | - [1. Features](#1-features) 21 | - [2. Installation](#2-installation) 22 | - [2.1 Download Installer](#--21-download-installer) 23 | - [2.2 Fork and Clone Repo](#--22-fork-and-clone-repo) 24 | - [3. How to Use](#3-how-to-use) 25 | - [3.1 Contract Builder](#31-contract-builder) 26 | - [3.2 Front Tester](#32-front-tester) 27 | - [3.3 Back Tester](#33-back-tester) 28 | - [3.4 Document Creator](#34-document-creator) 29 | - [4. Iteration Opportunities](#4-iteration-opportunities) 30 | - [5. Contributors](#5-contributors) 31 | 32 | ## 0. BRIEF 33 | 34 | Contractual is a tool designed to complement team-based development for web applications that utilize JS and Node.js. 35 | 36 | Contractual's design philosophy is centered around what we call "Contract driven development". 37 | 38 | Contract refers to the data contract between client and server. Contractual allows development teams to define their applications data contract using a simple user interface. 39 | 40 | Additionally, frontend/backend development teams will be able to test their applications confidently in a decoupled manner. 41 | 42 | ## 1. FEATURES 43 | 44 | - Define project data contracts within the app and share it using a 4-digit token 45 | - Test and validate request/response architecture with detailed contract breach analysis 46 | - Develop new features client-side or server-side independently of one-another while adhering to contract specifications with mock results 47 | - Generate clear contract documentation for distribution with a single click 48 | 49 | ## 2. INSTALLATION 50 | 51 | ### - 2.1 DOWNLOAD INSTALLER 52 | 53 | Contractual Desktop can be downloaded from our [website](https://www.contractualapp.io/). Available for Mac OS, Windows, and Linux. 54 | 55 | ##### OR 56 | 57 | ### - 2.2 FORK AND CLONE REPO 58 | 59 | Developers who want to dive deeper into the code can fork this repo. 60 | 61 | Contributions are not only welcome but highly recommended, we believe that every open source contribution makes the entire community much better. 62 | 63 | We put our database URI in the .env file for your testing convenience. Do NOT put any sensitive information in your account! 64 | 65 | If you would like the app and would use it, you should replace with your own database URI. 66 | 67 | 68 | #### elephantSQL directions: 69 | 70 | This application requires a postgreSQL database to function.Please create your postgreSQL database instane (we recommend elephantSQL as a free option). 71 | 72 | After creating your postgreSQL instance, refer to the build.sql file in the root of the app directory. This file will contain the proper query strings to build your own contractual database. Please run these commands in your postgreSQL database. 73 | 74 | In order to connect the application to your database instance, go to the _.env_ in the root directory, and paste your query connection string into the DB_KEY variable. 75 | 76 | ## 3. HOW TO USE 77 | 78 | ### 3.1 CONTRACT BUILDER 79 | 80 | The Contract tab is where the project lead will define or edit the data contract for their application. 81 | 82 | Users will select the desired request method option from a dropdown list and input the proper endpoint for their request to be sent to. 83 | 84 | Next, the user should add the key names and the expected datatypes of those keys for the request/response objects. 85 | 86 | Once the desired input fields are completed, the user can store this endpoint in their data contract by pressing save. 87 | 88 |
89 | 90 |
Demo_1: add an endpoint
91 |
92 |
93 | 94 | Alternatively, the user may select from the endpoint drop down list to view any endpoints that have been previously defined. 95 | 96 | These endpoints can be edited or deleted using the same method described above. 97 | 98 | A notable feature built into this application is the 4-digit identifier token generated uniquely for each created contract. 99 | 100 |
101 | 102 |
Demo_2: find the token
103 |
104 |
105 | 106 | As mentioned, this token is generated when a new contract is created. 107 | 108 | Members can join, view, and test with previously built data contracts by clicking “Join Contract” in the application settings and inputting a valid token along with the associated contract’s name. A correct contract name and token must be provided at the same time. 109 | 110 | Currently, contract edit access is only provided to the user who created the data contract. 111 | 112 |
113 | 114 |
Demo_3: import a contract with token
115 |
116 |
117 | 118 | Once a single data contract endpoint has been created or imported, the user is ready and able to begin testing their fullstack application or export the contract for distribution. 119 | 120 | ### 3.2 FRONT TESTER 121 | 122 | The Frontend tab is used for testing a frontend application’s http request functionality without requiring the addition of any server-side code. 123 | 124 | In order for the frontend developer to test with contractual. The will need to sent their requests to our testing server at **PORT 1234**. 125 | 126 | Upon sending requests from a frontend application, Contractual will display a log of these requests and whether or not the data contract was breached. 127 | 128 | If the request successfully adheres to the data contract, the user will also receive a mock response corresponding to the response defined in the contract builder for that endpoint. 129 | 130 | This tool allows frontend developers to test with confidence and receive clear, immediate feedback. 131 | 132 |
133 | 134 |
Demo_4: a successful request
135 |
136 |
137 | 138 |
139 | 140 |
Demo_5: a failed request with error analysis
141 |
142 |
143 | 144 |
145 | 146 |
Demo_6: a mock response based on the pre-defined data contract
147 |
148 |
149 | 150 | ### 3.3 BACK TESTER 151 | 152 | The Backend tab is used to test an application’s server-side request handling while being completely decoupled from any sort of front end. 153 | 154 | Those familiar with Postman will feel right at home with this tool. 155 | 156 | However, Contractual allows you to select from a list of endpoints defined in the data contract with easy to use input fields for the data values. 157 | 158 | Upon sending a request, the back tester will render the response from the application and whether or not the data contract was followed, or if the request failed. 159 | 160 |
161 | 162 |
Demo_7: a successful response
163 |
164 |
165 | 166 |
167 | 168 |
Demo_8: a failed response with error analysis
169 |
170 |
171 | 172 | ### 3.4 DOCUMENT CREATOR 173 | 174 | The document creator is used to view the data contract of the currently active contract in a simplified layout. You are able to export a pdf image of this page for quick and easy distribution to your team using the export button. 175 | 176 |
177 | 178 |
Demo_9: save the contract to local
179 |
180 |
181 | 182 | --- 183 | ## 4. ITERATION OPPORTUNITIES 184 | - Create Web application version of contractual 185 | - Add ability to select params for "GET" requests 186 | - Object support for backend mock response 187 | - Add raw test input field for complex request/response body structure 188 | - Add a usage description section for each endpoint in Document Creator page 189 | - Add additional export options to document creator page 190 | 191 | ## 5. CONTRIBUTORS 192 | 193 | [Ernest Leung](https://www.linkedin.com/in/ernestleung52/) [@ernestLeung52](https://github.com/ErnestLeung52) 194 | 195 | [George Jeng](https://www.linkedin.com/in/gjenga/) [@gdelaselva](https://github.com/gdelaselva) 196 | 197 | [Joseph Amos](https://www.linkedin.com/in/joe-amos/) [@joeamos](https://github.com/joeamos) 198 | 199 | [Yankun Song](https://www.linkedin.com/in/yankunsong/) [@yankun-song](https://github.com/yankun-song) 200 | -------------------------------------------------------------------------------- /assets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/contractual/d00ce86a5ddcc196a945f45e45b37c599bf967cb/assets/.DS_Store -------------------------------------------------------------------------------- /assets/img/app-logo-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/contractual/d00ce86a5ddcc196a945f45e45b37c599bf967cb/assets/img/app-logo-mac.png -------------------------------------------------------------------------------- /assets/img/app-logo-win.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/contractual/d00ce86a5ddcc196a945f45e45b37c599bf967cb/assets/img/app-logo-win.ico -------------------------------------------------------------------------------- /assets/img/app-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 10 | 14 | 25 | 36 | 39 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /assets/img/icon-black 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/contractual/d00ce86a5ddcc196a945f45e45b37c599bf967cb/assets/img/icon-black 2.png -------------------------------------------------------------------------------- /assets/img/icon-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/contractual/d00ce86a5ddcc196a945f45e45b37c599bf967cb/assets/img/icon-black.png -------------------------------------------------------------------------------- /assets/img/icon-user 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/contractual/d00ce86a5ddcc196a945f45e45b37c599bf967cb/assets/img/icon-user 2.png -------------------------------------------------------------------------------- /assets/img/icon-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/contractual/d00ce86a5ddcc196a945f45e45b37c599bf967cb/assets/img/icon-user.png -------------------------------------------------------------------------------- /assets/img/icon-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/contractual/d00ce86a5ddcc196a945f45e45b37c599bf967cb/assets/img/icon-white.png -------------------------------------------------------------------------------- /build.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE users( 2 | user_id SERIAL PRIMARY KEY, 3 | email VARCHAR NOT NULL UNIQUE, 4 | name VARCHAR NOT NULL, 5 | password VARCHAR NOT NULL 6 | ) 7 | 8 | CREATE TABLE contracts( 9 | contract_id SERIAL PRIMARY KEY, 10 | title VARCHAR NOT NULL, 11 | content VARCHAR NOT NULL, 12 | token VARCHAR NOT NULL, 13 | user_id INTEGER, 14 | FOREIGN KEY (user_id) REFERENCES users(user_id) 15 | ) 16 | 17 | CREATE TABLE users_contracts( 18 | user_id INTEGER, 19 | FOREIGN KEY (user_id) REFERENCES users(user_id), 20 | contract_id INTEGER, 21 | FOREIGN KEY (contract_id) REFERENCES contracts(contract_id), 22 | permission BOOLEAN 23 | ) -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Contractual 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "contractual", 3 | "version": "1.0.0", 4 | "description": "Contractual allows development teams to define their applications data contract using a simple user interface. Helping frontend/backend development to test their applications confidently in a decoupled manner", 5 | "main": "./dist/main.js", 6 | "moduleFileExtensions": [ 7 | "js", 8 | "jsx", 9 | "ts", 10 | "tsx", 11 | "json" 12 | ], 13 | "scripts": { 14 | "test": "jest", 15 | "build": "webpack --config ./webpack.config.js", 16 | "watch": "webpack --config webpack.config.js --watch", 17 | "start": "npm run build && electron ./dist/main.js", 18 | "dev": "electron ./dist/main.js", 19 | "mac:installer": "electron-builder --mac", 20 | "win:installer": "electron-builder --win", 21 | "linux:installer": "electron-builder --linux" 22 | }, 23 | "build": { 24 | "productName": "Contractual", 25 | "appId": "com.electron.contractual", 26 | "directories": { 27 | "app": ".", 28 | "output": "installer" 29 | }, 30 | "files": [ 31 | "dist/**/*", 32 | "assets/**/*", 33 | "package.json", 34 | "src/**/*", 35 | "node_modules/**/*", 36 | ".env" 37 | ], 38 | "mac": { 39 | "category": "public.app-category.developer-tools", 40 | "icon": "assets/img/app-logo-mac.png", 41 | "hardenedRuntime": true, 42 | "gatekeeperAssess": false 43 | }, 44 | "dmg": { 45 | "backgroundColor": "#ffffff", 46 | "window": { 47 | "width": "400", 48 | "height": "300" 49 | }, 50 | "contents": [ 51 | { 52 | "x": 100, 53 | "y": 100 54 | }, 55 | { 56 | "x": 300, 57 | "y": 100, 58 | "type": "link", 59 | "path": "/Applications" 60 | } 61 | ] 62 | }, 63 | "win": { 64 | "target": [ 65 | "nsis" 66 | ], 67 | "icon": "assets/img/app-logo-win.ico" 68 | }, 69 | "nsis": { 70 | "oneClick": false, 71 | "installerIcon": "assets/img/app-logo-win.ico", 72 | "uninstallerIcon": "assets/img/app-logo-win.ico", 73 | "uninstallDisplayName": "Contractual-uninstaller", 74 | "license": "license.md", 75 | "allowToChangeInstallationDirectory": true 76 | }, 77 | "linux": { 78 | "target": [ 79 | "deb", 80 | "rpm", 81 | "AppImage" 82 | ], 83 | "category": "Development" 84 | } 85 | }, 86 | "keywords": [ 87 | "Data Contract", 88 | "Contract", 89 | "Test", 90 | "Endpoints" 91 | ], 92 | "author": { 93 | "name": "Contractual", 94 | "url": "https://www.contractualapp.io/", 95 | "email": "admin@contractualapp.io" 96 | }, 97 | "contributors": [ 98 | { 99 | "name": "Ernest Leung", 100 | "url": "https://github.com/ErnestLeung52", 101 | "email": "ernestleung52@gmail.com" 102 | }, 103 | { 104 | "name": "George Jeng", 105 | "url": "https://github.com/gdelaselva", 106 | "email": "gjenga@icloud.com" 107 | }, 108 | { 109 | "name": "Joe Amos", 110 | "url": "https://github.com/joeamos", 111 | "email": "joeamos17@gmail.com" 112 | }, 113 | { 114 | "name": "Yankun Song", 115 | "url": "https://github.com/yankun-song", 116 | "email": "yankun.l.song@gmail.com" 117 | } 118 | ], 119 | "license": "MIT", 120 | "repository": { 121 | "type": "git", 122 | "url": "https://github.com/oslabs-beta/contractual.git" 123 | }, 124 | "bugs": { 125 | "url": "https://github.com/oslabs-beta/contractual/issues" 126 | }, 127 | "dependencies": { 128 | "@headlessui/react": "^1.5.0", 129 | "@heroicons/react": "^1.0.6", 130 | "@reduxjs/toolkit": "^1.8.1", 131 | "axios": "^0.26.1", 132 | "bcrypt": "^5.0.1", 133 | "chance": "^1.1.8", 134 | "cors": "^2.8.5", 135 | "dotenv": "^16.0.0", 136 | "express": "^4.17.3", 137 | "file-loader": "^6.2.0", 138 | "formik": "^2.2.9", 139 | "html2canvas": "^1.4.1", 140 | "http": "^0.0.1-security", 141 | "jspdf": "^2.5.1", 142 | "pg": "^8.7.3", 143 | "react-redux": "^7.2.8", 144 | "react-router-dom": "^6.3.0", 145 | "redux": "^4.1.2", 146 | "redux-devtools-extension": "^2.13.9", 147 | "redux-thunk": "^2.4.1", 148 | "shell": "^0.9.4", 149 | "socket.io": "^4.4.1", 150 | "url-loader": "^4.1.1", 151 | "websocket": "^1.0.34", 152 | "ws": "^8.5.0", 153 | "yup": "^0.32.11" 154 | }, 155 | "devDependencies": { 156 | "@babel/core": "^7.17.8", 157 | "@babel/preset-env": "^7.16.11", 158 | "@babel/preset-react": "^7.16.7", 159 | "@tailwindcss/aspect-ratio": "^0.4.0", 160 | "@tailwindcss/forms": "^0.5.0", 161 | "@tailwindcss/line-clamp": "^0.3.1", 162 | "@tailwindcss/typography": "^0.5.2", 163 | "@types/react": "^17.0.43", 164 | "@types/react-dom": "^17.0.14", 165 | "@types/react-redux": "^7.1.23", 166 | "@types/yup": "^0.29.13", 167 | "autoprefixer": "^10.4.4", 168 | "babel-loader": "^8.2.4", 169 | "concurrently": "^7.1.0", 170 | "css-loader": "^6.7.1", 171 | "dotenv-webpack": "^7.1.0", 172 | "electron": "^18.0.2", 173 | "electron-builder": "^23.0.3", 174 | "html-webpack-plugin": "^5.5.0", 175 | "jest": "^27.5.1", 176 | "postcss": "^8.4.12", 177 | "postcss-loader": "^6.2.1", 178 | "react": "^18.0.0", 179 | "react-dom": "^18.0.0", 180 | "sass": "^1.49.11", 181 | "sass-loader": "^12.6.0", 182 | "style-loader": "^3.3.1", 183 | "tailwindcss": "^3.0.23", 184 | "ts-loader": "^9.2.8", 185 | "typescript": "^4.6.3", 186 | "webpack": "^5.71.0", 187 | "webpack-cli": "^4.9.2" 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | /* eslint global-require: off, import/no-extraneous-dependencies: off */ 2 | 3 | module.exports = { 4 | plugins: [require('tailwindcss'), require('autoprefixer')], 5 | }; -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* Splash Page */ 6 | 7 | /* 8 | .contractual-logo { 9 | width: 55px; 10 | height: 55px; 11 | opacity: 1; 12 | animation: logo 3s ease-in 1; 13 | } 14 | 15 | @keyframes logo { 16 | 0% { 17 | opacity: 0%; 18 | } 19 | 60% { 20 | opacity: 80%; 21 | } 22 | 100% { 23 | opacity: 100%; 24 | } 25 | } 26 | 27 | .splash { 28 | background-color: black; 29 | margin: 0; 30 | padding: 0; 31 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, 32 | Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 33 | } 34 | 35 | .center { 36 | display: flex; 37 | flex-direction: column; 38 | text-align: center; 39 | justify-content: center; 40 | align-items: center; 41 | min-height: 100vh; 42 | } 43 | 44 | .ring { 45 | position: absolute; 46 | width: 160px; 47 | height: 160px; 48 | border-radius: 50%; 49 | animation: ring 3s linear infinite; 50 | } 51 | 52 | @keyframes ring { 53 | 0% { 54 | transform: rotate(0deg); 55 | box-shadow: 1px 5px 2px #020c65; 56 | } 57 | 50% { 58 | transform: rotate(180deg); 59 | box-shadow: 1px 5px 2px #0f76cf; 60 | } 61 | 100% { 62 | transform: rotate(359deg); 63 | box-shadow: 1px 5px 2px #020c65; 64 | } 65 | } 66 | 67 | .ring:before { 68 | position: absolute; 69 | content: ''; 70 | left: 0; 71 | top: 0; 72 | height: 100%; 73 | width: 100%; 74 | border-radius: 50%; 75 | box-shadow: 0 0 5px rgba(255, 255, 255, 0.3); 76 | } 77 | 78 | .center span { 79 | color: #000000; 80 | font-size: 14px; 81 | text-transform: uppercase; 82 | letter-spacing: 1px; 83 | line-height: 35px; 84 | animation: text 3s ease-in-out infinite; 85 | } 86 | 87 | .center footer { 88 | color: rgb(136, 134, 134); 89 | font-size: 10px; 90 | position: absolute; 91 | bottom: 3%; 92 | right: 3%; 93 | } 94 | 95 | @keyframes text { 96 | 60% { 97 | color: rgb(100, 99, 99); 98 | } 99 | } 100 | 101 | */ 102 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { MemoryRouter as Router, Routes, Route } from 'react-router-dom'; 2 | import './App.css'; 3 | import Navbar from './components/Navbar'; 4 | import ContractBuilder from './containers/ContractBuilder'; 5 | import FrontTester from './containers/FrontTester'; 6 | import DocumentCreator from './containers/DocumentCreator'; 7 | import BackTester from './containers/BackTester'; 8 | import Login from './components/Login'; 9 | import Register from './components/Register'; 10 | 11 | export default function App() { 12 | 13 | return ( 14 |
15 | 16 |
17 | 18 | } /> 19 | } /> 20 | } /> 21 | } > 22 | } /> 23 | } /> 24 | } /> 25 | } /> 26 | } /> 27 | 28 | 29 |
30 |
31 |
32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/React.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import App from './App'; 5 | import { store } from './state/store'; 6 | import ReactDOM from 'react-dom/client' 7 | 8 | // render( 9 | // 10 | // 11 | // , 12 | // document.getElementById('root') 13 | // ); 14 | 15 | 16 | const root = ReactDOM.createRoot(document.getElementById("root")); 17 | root.render( 18 | 19 | 20 | , 21 | ); -------------------------------------------------------------------------------- /src/components/BackEndpoint.tsx: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { useState } from "react"; 3 | import { CheckIcon, SelectorIcon } from "@heroicons/react/solid"; 4 | import { Combobox } from "@headlessui/react"; 5 | import { checkInput } from "../express/testing_server/controllers/contractOp"; 6 | import { useDispatch, useSelector } from "react-redux"; 7 | import { RootState } from "../state/store"; 8 | import { updateLog } from "../state/features/backLogSlice"; 9 | 10 | interface EnumEndpointItem { 11 | id: number; 12 | method: string; 13 | name: string; 14 | } 15 | // type Contracts = { 16 | // [key: string]: string; 17 | // }; 18 | type KeyTypeValue = { 19 | reqKey: string; 20 | reqValType: string; 21 | // reqVal: string | number | boolean | any[]; 22 | reqVal: string; 23 | }; 24 | type BodyInputs = KeyTypeValue[]; 25 | // interface ContractEndpointProps { 26 | // reqMethod: string; 27 | // setReqMethod: (e: any) => void; 28 | // URLString: string; 29 | // setURLString: (e: any) => void; 30 | // reqInputs: BodyInputs; 31 | // setReqInputs: (index: string, e: Event) => void; 32 | // updateReqFields: (index: string, e: Event) => void; 33 | // // resetFields: () => void; 34 | // endpoints: EnumEndpointItem[]; 35 | // currentContract: Contracts; 36 | // } 37 | 38 | function classNames(...classes) { 39 | return classes.filter(Boolean).join(" "); 40 | } 41 | 42 | export default function BackEndpoint({ 43 | reqMethod, 44 | setReqMethod, 45 | reqInputs, 46 | setReqInputs, 47 | updateReqFields, 48 | // resetFields, 49 | URLString, 50 | setURLString, 51 | endpoints, 52 | currentContract, 53 | }) { 54 | const [query, setQuery] = useState(""); 55 | const [selectedEndpoint, setSelectedEndpoint] = useState(); 56 | // const [responses, updateResponses] = useState([]); 57 | const currentLog = useSelector((store: RootState) => store.backLog); 58 | const dispatch = useDispatch(); 59 | 60 | /** SEARCH FILTER FOR ENDPOINT INPUT FIELD */ 61 | const filteredEndpoints = 62 | query === "" 63 | ? endpoints 64 | : endpoints.filter((endpoint: EnumEndpointItem) => { 65 | return endpoint.name.toLowerCase().includes(query.toLowerCase()); 66 | }); 67 | 68 | const sendRequest = ( 69 | URLString: string, 70 | reqMethod: string, 71 | endpoint: EnumEndpointItem, 72 | reqInputs: BodyInputs 73 | ): void => { 74 | const reqBody = {}; 75 | const condition = `Res@${reqMethod}@${endpoint.name}`; //endpoint is entire url string 76 | 77 | reqInputs.forEach((input) => { 78 | //PARSE NON STRINGS? 79 | let nonString; 80 | if (input.reqValType !== "string") { 81 | if (input.reqValType === "boolean") { 82 | if (input.reqVal === "true") { 83 | nonString = true; 84 | } else nonString = false; 85 | } else if (input.reqValType === "number") 86 | nonString = Number(input.reqVal); 87 | else if (input.reqValType === "array-any-any") 88 | nonString = JSON.parse(input.reqVal); 89 | 90 | reqBody[input.reqKey] = nonString; 91 | } else reqBody[input.reqKey] = input.reqVal; 92 | // reqBody[input.reqKey] = input.reqVal 93 | }); 94 | // perform ajax request passing in built request body from above 95 | // use template literals to send to right endpoints 96 | // need to perform data contract check 97 | function checkResponse(response, contract, condition) { 98 | const report = checkInput(response, contract, condition); 99 | return report; 100 | } 101 | 102 | // function getTime() { 103 | // const today = new Date(); 104 | // const date = today.getMonth() + 1 + "/" + today.getDate(); 105 | // const time = 106 | // today.getHours() + 107 | // ":" + 108 | // today.getMinutes() + 109 | // ":" + 110 | // String(today.getSeconds()).padStart(2, "0"); 111 | // return time + " [" + date + "]"; 112 | // } 113 | function getTime() { 114 | const today = new Date(); 115 | const date = today.getMonth() + 1 + "/" + today.getDate(); 116 | const time = 117 | String(today.getHours()).padStart(2, "0") + 118 | ":" + 119 | String(today.getMinutes()).padStart(2, "0") + 120 | ":" + 121 | String(today.getSeconds()).padStart(2, "0"); 122 | return time + " [" + date + "]"; 123 | } 124 | 125 | if (reqMethod === "GET") { 126 | axios 127 | .get(URLString + endpoint.name) 128 | .then((response) => { 129 | const report = checkResponse( 130 | response.data, 131 | currentContract, 132 | condition 133 | ); 134 | report.endpoint = endpoint.name; 135 | report.method = reqMethod; 136 | report.time = getTime(); 137 | dispatch(updateLog(report)); 138 | }) 139 | .catch((error) => { 140 | dispatch( 141 | updateLog({ 142 | endpoint: endpoint.name, 143 | error: ["Request failure"], 144 | method: reqMethod, 145 | pass: false, 146 | time: getTime(), 147 | }) 148 | ); 149 | }); 150 | } else if (reqMethod === "POST") { 151 | axios 152 | .post(URLString + endpoint.name, reqBody) 153 | .then((response) => { 154 | const report = checkResponse( 155 | response.data, 156 | currentContract, 157 | condition 158 | ); 159 | report.endpoint = endpoint.name; 160 | report.method = reqMethod; 161 | report.time = getTime(); 162 | dispatch(updateLog(report)); 163 | }) 164 | .catch((error) => { 165 | dispatch( 166 | updateLog({ 167 | endpoint: endpoint.name, 168 | error: ["Request failure"], 169 | method: reqMethod, 170 | pass: false, 171 | time: getTime(), 172 | }) 173 | ); 174 | }); 175 | } else if (reqMethod === "PUT") { 176 | axios 177 | .put(URLString + endpoint.name, reqBody) 178 | .then((response) => { 179 | const report = checkResponse( 180 | response.data, 181 | currentContract, 182 | condition 183 | ); 184 | report.endpoint = endpoint.name; 185 | report.method = reqMethod; 186 | report.time = getTime(); 187 | dispatch(updateLog(report)); 188 | }) 189 | .catch((error) => { 190 | dispatch( 191 | updateLog({ 192 | endpoint: endpoint.name, 193 | error: ["Request failure"], 194 | method: reqMethod, 195 | pass: false, 196 | time: getTime(), 197 | }) 198 | ); 199 | }); 200 | } else if (reqMethod === "PATCH") { 201 | axios 202 | .patch(URLString + endpoint.name, reqBody) 203 | .then((response) => { 204 | const report = checkResponse( 205 | response.data, 206 | currentContract, 207 | condition 208 | ); 209 | report.endpoint = endpoint.name; 210 | report.method = reqMethod; 211 | report.time = getTime(); 212 | dispatch(updateLog(report)); 213 | }) 214 | .catch((error) => { 215 | dispatch( 216 | updateLog({ 217 | endpoint: endpoint.name, 218 | error: ["Request failure"], 219 | method: reqMethod, 220 | pass: false, 221 | time: getTime(), 222 | }) 223 | ); 224 | }); 225 | } else if (reqMethod === "DELETE") { 226 | axios 227 | .delete(URLString + endpoint.name, reqBody) 228 | .then((response) => { 229 | const report = checkResponse( 230 | response.data, 231 | currentContract, 232 | condition 233 | ); 234 | report.endpoint = endpoint.name; 235 | report.method = reqMethod; 236 | report.time = getTime(); 237 | dispatch(updateLog(report)); 238 | }) 239 | .catch((error) => { 240 | dispatch( 241 | updateLog({ 242 | endpoint: endpoint.name, 243 | error: ["Request failure"], 244 | method: reqMethod, 245 | pass: false, 246 | time: getTime(), 247 | }) 248 | ); 249 | }); 250 | } 251 | //Reset form fields 252 | }; 253 | // add new button to reset fields 254 | // resetFields() 255 | return ( 256 |
257 |
258 |
259 |
260 |
264 | {reqMethod} 265 |
266 |
267 |
268 |
269 |
270 |
271 | setURLString(e)} 277 | className="shadow-sm placeholder-green-500 bg-gray-900 text-gray-50 focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" 278 | placeholder="https://domain" 279 | /> 280 |
281 |
282 |
283 | 284 |
285 | { 289 | setSelectedEndpoint(endpoint); 290 | setReqMethod(endpoint.method.toUpperCase()); 291 | updateReqFields( 292 | `Req@${endpoint.method.toUpperCase()}@${endpoint.name}` 293 | ); 294 | }} 295 | > 296 |
297 | setQuery(event.target.value)} 300 | placeholder="Endpoint" 301 | displayValue={(endpoint: EnumEndpointItem) => endpoint.name} 302 | /> 303 | 304 | 309 | 310 | {filteredEndpoints.length > 0 && ( 311 | 312 | {filteredEndpoints.map((endpoint) => ( 313 | 317 | classNames( 318 | "relative cursor-default select-none py-2 pl-3 pr-9", 319 | active ? "bg-blue-600 text-white" : "text-gray-900" 320 | ) 321 | } 322 | > 323 | {({ active, selected }) => ( 324 | <> 325 | 331 | {endpoint.method + " " + endpoint.name} 332 | 333 | 334 | {selected && ( 335 | 341 | 346 | )} 347 | 348 | )} 349 | 350 | ))} 351 | 352 | )} 353 |
354 |
355 |
356 |
357 | 366 |
367 |
368 |
369 | ); 370 | } 371 | -------------------------------------------------------------------------------- /src/components/BackLog.tsx: -------------------------------------------------------------------------------- 1 | import { useSelector } from 'react-redux'; 2 | import { RootState } from '../state/store'; 3 | 4 | export default function BackLog() { 5 | const currentLog = useSelector((store: RootState) => store.backLog); 6 | 7 | return ( 8 |
9 |
10 |
11 |
12 |

Response Log

13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | 21 | 22 | 23 | 26 | 29 | 32 | 35 | {/* */} 38 | 39 | 40 | 41 | {currentLog.map((request, index) => { 42 | const details = []; 43 | for (let i = 0; i < request.error.length; i++) { 44 | details.push(
{request.error[i]}

) 45 | } 46 | let reqStatus; 47 | if (request.pass === true) { 48 | reqStatus = ( 49 | 50 | Success 51 | 52 | ); 53 | } else if (request.pass === false) { 54 | reqStatus = ( 55 | 56 | Error 57 | 58 | ); 59 | } 60 | 8; 61 | return ( 62 | 63 | 75 | 78 | 81 | 84 | 85 | ); 86 | })} 87 | 88 |
24 | Endpoint 25 | 27 | Status 28 | 30 | Time 31 | 33 | Report 34 | 36 | Edit 37 |
64 |
65 |
66 |
67 | {request.endpoint} 68 |
69 |
70 | {request.method} 71 |
72 |
73 |
74 |
76 | {reqStatus} 77 | 79 | {request.time} 80 | 82 | {details} 83 |
89 |
90 |
91 |
92 |
93 |
94 | ) 95 | } -------------------------------------------------------------------------------- /src/components/BackRequestEditor.tsx: -------------------------------------------------------------------------------- 1 | /////TESTING DYNAMIC INPUTS 2 | // type KeyAndType = { 3 | // [key: string]: string; 4 | // }; 5 | // type BodyInputs = KeyAndType[]; 6 | // /// TEST END/////// 7 | 8 | 9 | // type KeyTypeValue = { 10 | // reqKey: string; 11 | // reqValType: string; 12 | // reqVal: string | number | boolean | any[] 13 | // }; 14 | // type BodyInputs = KeyTypeValue[] 15 | // interface ContractEndpointProps { 16 | // reqInputs: BodyInputs; 17 | // setReqInputs: (index: string, e: Event) => void; 18 | // reqMethod: string 19 | // } 20 | export default function BackRequestEditor({ 21 | reqInputs, 22 | setReqInputs, 23 | reqMethod 24 | }) { 25 | 26 | return ( 27 | <> 28 |
29 | 30 |
31 |
32 |
33 |

34 | Mock Request 35 |

36 |
37 |
38 | {/*
*/} 39 |
40 |
41 | 50 |
51 |
52 | {/*
*/} 53 | {/* ////////// Render this portion dynamically */} 54 | {reqInputs.map((input, index) => { 55 | const stringInput = ( 56 | setReqInputs(index, e)} 65 | className='shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-blue-500 bg-gray-800 text-gray-50 rounded-md' 66 | /> 67 | ); 68 | const numberInput = ( 69 | setReqInputs(index, e)} 78 | className='shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-blue-500 bg-gray-800 text-gray-50 rounded-md' 79 | /> 80 | ); 81 | const booleanInput = ( 82 | 93 | ) 94 | const arrayInput = ( 95 | setReqInputs(index, e)} 105 | className='shadow-sm placeholder-green-500 focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-blue-500 bg-gray-800 text-gray-50 rounded-md' 106 | /> 107 | ) 108 | return ( 109 |
110 |
111 | {/*
112 |
113 | 122 |
123 |
*/} 124 |
125 | 131 |
132 | setReqInputs(index, e)} 140 | className='shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-blue-500 bg-gray-800 text-blue-500 rounded-md' 141 | /> 142 |
143 |
144 | 145 |
146 | 152 |
153 | 167 |
168 |
169 | 170 |
171 | 177 |
178 | {(() => { 179 | if (reqInputs[index].reqValType === 'boolean') return booleanInput; 180 | else if (reqInputs[index].reqValType === 'number') return numberInput; 181 | else if (reqInputs[index].reqValType === 'string') return stringInput; 182 | else if (reqInputs[index].reqValType === 'array-any-any') return arrayInput; 183 | })()} 184 |
185 |
186 |
187 |
188 |
189 | ); 190 | })} 191 |
192 |
193 | 194 | ) 195 | } -------------------------------------------------------------------------------- /src/components/ContractEditor.tsx: -------------------------------------------------------------------------------- 1 | /////TESTING DYNAMIC INPUTS 2 | type KeyAndType = { 3 | [key: string]: string; 4 | }; 5 | type BodyInputs = KeyAndType[]; 6 | /// TEST END/////// 7 | 8 | interface ContractEndpointProps { 9 | reqInputs: BodyInputs; 10 | resInputs: BodyInputs; 11 | setReqInputs: (index: string, e: Event) => void; 12 | setResInputs: (index: string, e: Event) => void; 13 | addReqField: () => void; 14 | addResField: () => void; 15 | subtractReqField: () => void; 16 | subtractResField: () => void; 17 | } 18 | 19 | export default function ContractEditor({ 20 | reqInputs, 21 | resInputs, 22 | setReqInputs, 23 | setResInputs, 24 | addReqField, 25 | addResField, 26 | subtractReqField, 27 | subtractResField 28 | }) { 29 | 30 | return ( 31 |
32 |
33 |
34 |
35 |

36 | Request 37 |

38 |

from frontend

39 |
40 |
41 | {/*
*/} 42 |
43 |
44 | 53 |
54 |
55 | 56 | {/* ////////// Render this portion dynamically */} 57 | {reqInputs.map((input, index) => { 58 | return ( 59 |
60 |
61 | {/*
62 |
63 | 72 |
73 |
*/} 74 | 75 |
76 | 82 |
83 | setReqInputs(index, e)} 90 | className='shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-blue-500 bg-gray-800 text-gray-50 rounded-md' 91 | /> 92 |
93 |
94 | 95 |
96 | 102 |
103 | 116 |
117 |
118 |
119 |
120 | ); 121 | })} 122 | 123 | 143 | {Array.isArray(reqInputs) && reqInputs.length > 1 ? : null} 151 |
152 | 153 |
154 |
155 |
156 |

157 | Response 158 |

159 |

from backend

160 |
161 |
162 |
163 |
164 | 175 |
176 |
177 | 178 | {/* dynamic portion test */} 179 | {resInputs.map((input, index) => { 180 | return ( 181 |
182 |
183 | {/*
184 |
185 | 196 |
197 |
*/} 198 | 199 |
200 | 206 |
207 | setResInputs(index, e)} 213 | className='shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-blue-500 bg-gray-800 text-gray-50 rounded-md' 214 | /> 215 |
216 |
217 | 218 |
219 | 225 |
226 | 239 |
240 |
241 |
242 |
243 | ); 244 | })} 245 | 246 | 266 | {resInputs.length > 1 ? : null} 274 | 275 |
276 |
277 | ); 278 | } 279 | -------------------------------------------------------------------------------- /src/components/ContractEndpoint.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Combobox } from '@headlessui/react'; 3 | import { CheckIcon, SelectorIcon } from '@heroicons/react/solid'; 4 | import { useDispatch, useSelector } from 'react-redux'; 5 | import { RootState } from '../state/store'; 6 | import { updateContract } from '../state/features/contractSlice'; 7 | import axios from 'axios'; 8 | 9 | interface EnumEndpointItem { 10 | id: number; 11 | method: string 12 | name: string; 13 | } 14 | // type Contracts = { 15 | // [key: string]: string; 16 | // }; 17 | type KeyAndType = { 18 | [key: string]: string; 19 | }; 20 | type BodyInputs = KeyAndType[]; 21 | interface ContractEndpointProps { 22 | reqMethod: string, 23 | setReqMethod; 24 | handleSetReqMethod: (e: any) => void, 25 | newEndpoint: string, 26 | endpoints: EnumEndpointItem[], 27 | setEndpoint: (e: any) => void, 28 | setNewEndpoint: (input: string) => void 29 | reqInputs: BodyInputs, 30 | resInputs: BodyInputs, 31 | resetFields: () => void, 32 | updateFieldsByEndpoint: (requestString: string, responseString: string) => void, 33 | setNotificationString: (input: string) => void, 34 | setVisibility: (input: boolean) => void, 35 | setPositiveFeedback: (input: boolean) => void, 36 | } 37 | function classNames(...classes) { 38 | return classes.filter(Boolean).join(' '); 39 | } 40 | 41 | const ContractEndpoint: React.FC = ({ 42 | reqMethod, 43 | setReqMethod, 44 | handleSetReqMethod, 45 | newEndpoint, 46 | endpoints, 47 | setNewEndpoint, 48 | setEndpoint, 49 | reqInputs, 50 | resInputs, 51 | resetFields, 52 | updateFieldsByEndpoint, 53 | setNotificationString, 54 | setVisibility, 55 | setPositiveFeedback, 56 | }): JSX.Element => { 57 | const [query, setQuery] = useState(''); 58 | const [selectedEndpoint, setSelectedEndpoint] = useState(); 59 | const store = useSelector((store: RootState) => store.contract) 60 | const { currentContract, currentContractToken, owns } = useSelector((store: RootState) => store.contract); 61 | const dispatch = useDispatch() 62 | 63 | 64 | //// TESTING 65 | /** DESELECT CURRENTLY SELECTED ENDPOINT WHEN BACKSPACING THE ENDPOINT STRING AFTER CHOOSING FROM DROPDOWN 66 | * 67 | */ 68 | const handleEndpointDeselect = () => { 69 | if (newEndpoint === '') { 70 | setSelectedEndpoint(undefined) 71 | } 72 | } 73 | ///// TESTING 74 | /** SEND CURRENT ACTIVE CONTRACT TOKEN TO WEBHOOK TESTING SERVER TO UPDATE EXPECTED EXPECTED REQUEST ENDPOINTS */ 75 | const sendToken = (token) => { 76 | axios 77 | .get(`http://localhost:1234/contract/${token}`) 78 | .then((response) => { 79 | }) 80 | .catch((error) => { 81 | console.log('Error is: ', error) 82 | }) 83 | } 84 | /** SAVE OR UPDATE THE CURRENTLY SELECTED ENDPOINT AND FIELDS TO THE DATABASE */ 85 | const saveContract = ( 86 | reqMethod: string, 87 | endpoint: string, 88 | reqInputs: BodyInputs, 89 | resInputs: BodyInputs 90 | ): void => { 91 | 92 | 93 | /// NOTIFICATION TEST 94 | if (!currentContractToken) { 95 | setNotificationString('NO CURRENT CONTRACT SELECTED') 96 | setPositiveFeedback(false) 97 | setVisibility(true) 98 | return 99 | } 100 | else if (!endpoint) { 101 | setNotificationString('ENDPOINT REQUIRED') 102 | setPositiveFeedback(false) 103 | setVisibility(true) 104 | return 105 | } 106 | else if (!owns.includes(currentContractToken)) { 107 | setNotificationString('ONLY CONTRACT OWNER MAY EDIT THIS CONTRACT') 108 | setPositiveFeedback(false) 109 | setVisibility(true) 110 | return 111 | } 112 | //// NOTIFICATION TEST END 113 | 114 | const reqBody = {}; 115 | const resBody = {}; 116 | const newContract = {}; 117 | 118 | reqInputs.forEach((input) => { 119 | reqBody[input.reqKey] = input.reqValType; 120 | }); 121 | resInputs.forEach((input) => { 122 | resBody[input.resKey] = input.resValType; 123 | }); 124 | 125 | /** BUILD CONTRACT STRINGS FOR REQUEST AND RESPONSE */ 126 | newContract[`Req@${reqMethod}@${endpoint}`] = reqBody; // should pass in request object here 127 | newContract[`Res@${reqMethod}@${endpoint}`] = resBody; // should pass in response object here 128 | 129 | const contractCopy = { ...currentContract, ...newContract } 130 | 131 | axios 132 | .patch('http://localhost:4321/contract', { 133 | content: contractCopy, 134 | token: currentContractToken 135 | }) 136 | .then((response) => { 137 | if (response.status === 200) { 138 | dispatch(updateContract(contractCopy)) 139 | resetFields() 140 | sendToken(currentContractToken) 141 | } 142 | }) 143 | .catch((error) => { 144 | console.log(error); 145 | }); 146 | 147 | setNotificationString('ENDPOINT SAVED'); 148 | setPositiveFeedback(true); 149 | setVisibility(true); 150 | } 151 | 152 | const deleteEndpoint = (reqMethod: string, endpoint: string) => { 153 | if (!currentContractToken) { 154 | setNotificationString('NO CURRENT CONTRACT SELECTED'); 155 | setPositiveFeedback(false); 156 | setVisibility(true); 157 | return 158 | } 159 | else if (!endpoint) { 160 | setNotificationString('ENDPOINT REQUIRED'); 161 | setPositiveFeedback(false); 162 | setVisibility(true); 163 | return 164 | } 165 | else if (!owns.includes(currentContractToken)) { 166 | setNotificationString('ONLY CONTRACT OWNER MAY EDIT THIS CONTRACT'); 167 | setPositiveFeedback(false); 168 | setVisibility(true); 169 | return 170 | } 171 | const contractCopy = { ...currentContract } 172 | 173 | delete contractCopy[`Req@${reqMethod}@${endpoint}`] 174 | delete contractCopy[`Res@${reqMethod}@${endpoint}`] 175 | 176 | axios 177 | .patch('http://localhost:4321/contract', { 178 | content: contractCopy, 179 | token: currentContractToken 180 | }) 181 | .then((response) => { 182 | if (response.status === 200) { 183 | dispatch(updateContract(contractCopy)) 184 | resetFields() 185 | sendToken(currentContractToken) 186 | } 187 | }) 188 | .catch((error) => { 189 | console.log(error); 190 | }); 191 | 192 | setNotificationString('ENDPOINT DELETED'); 193 | setPositiveFeedback(true); 194 | setVisibility(true); 195 | } 196 | 197 | /** ADJUST CURRENT SELECTED ENUM INDEX */ 198 | const endpointChange = (event) => { 199 | setQuery(event.target.value); 200 | setEndpoint(event); 201 | 202 | /////// TESTING 203 | // if (newEndpoint === '') { 204 | // setSelectedEndpoint(undefined) 205 | // } 206 | /////// TESTING 207 | }; 208 | 209 | /** SEARCH FILTER FOR ENDPOINT INPUT FIELD */ 210 | const filteredEndpoints = 211 | query === '' 212 | ? endpoints 213 | : endpoints.filter((endpoint: EnumEndpointItem) => { 214 | return endpoint.name.toLowerCase().includes(query.toLowerCase()); 215 | }); 216 | 217 | return ( 218 |
219 |
220 |
221 | 236 |
237 | 238 | {/* TEST BUTTONS */} 239 | {/* 240 | 241 | */} 242 |
243 | { setSelectedEndpoint(endpoint); updateFieldsByEndpoint(`Req@${endpoint.method.toUpperCase()}@${endpoint.name}`, `Res@${endpoint.method.toUpperCase()}@${endpoint.name}`); setNewEndpoint(endpoint.name); setReqMethod(endpoint.method.toUpperCase()) }} 253 | > 254 |
255 | endpointChange(event)} 263 | displayValue={(endpoint: EnumEndpointItem) => endpoint.name} 264 | /> 265 | 266 | 271 | 272 | {filteredEndpoints.length > 0 && ( 273 | 274 | {filteredEndpoints.map((endpoint) => ( 275 | 279 | classNames( 280 | 'relative cursor-default select-none py-2 pl-3 pr-9', 281 | active ? 'bg-blue-600 text-white' : 'text-gray-900' 282 | ) 283 | } 284 | > 285 | {({ active, selected }) => ( 286 | <> 287 | 293 | {endpoint.method + ' ' + endpoint.name} 294 | 295 | 296 | {selected && ( 297 | 303 | 308 | )} 309 | 310 | )} 311 | 312 | ))} 313 | 314 | )} 315 |
316 |
317 |
318 | 319 |
320 | 328 |
329 |
330 | 338 |
339 |
340 |
341 | ); 342 | }; 343 | 344 | export default ContractEndpoint; -------------------------------------------------------------------------------- /src/components/DocumentExport.tsx: -------------------------------------------------------------------------------- 1 | function classNames(...classes) { 2 | return classes.filter(Boolean).join(" "); 3 | } 4 | interface ExportProps { 5 | handleDownloadPdf: () => void; 6 | fileName: string; 7 | setFileName: (input: string) => void; 8 | } 9 | const DocumentExport: React.FC = ({ 10 | handleDownloadPdf, 11 | fileName, 12 | setFileName, 13 | }): JSX.Element => { 14 | return ( 15 |
16 |
17 |
18 |
19 | 22 | setFileName(e.target.value)} 28 | className="shadow-sm placeholder-green-500 focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-blue-500 bg-gray-800 text-gray-50 rounded-md" 29 | placeholder="Filename" 30 | /> 31 |
32 |
33 | 34 | {/*
35 |
36 | 46 |
47 |
*/} 48 | 49 |
50 | 57 |
58 |
59 |
60 | ); 61 | }; 62 | 63 | export default DocumentExport; 64 | -------------------------------------------------------------------------------- /src/components/DocumentHeading.tsx: -------------------------------------------------------------------------------- 1 | import { useSelector } from "react-redux"; 2 | import { RootState } from '../state/store'; 3 | 4 | interface DocumentHeadingProps { 5 | 6 | } 7 | 8 | const DocumentHeading: React.FC = (): JSX.Element => { 9 | const { tokens, currentContractToken } = useSelector((store: RootState) => store.contract); 10 | 11 | let activeContract = ''; 12 | for (let token in tokens) { 13 | if (tokens[token] === currentContractToken) activeContract = token 14 | } 15 | return ( 16 |
17 |
18 |

{activeContract}

19 |

Project description.

20 |
21 |
22 | ) 23 | } 24 | 25 | export default DocumentHeading; -------------------------------------------------------------------------------- /src/components/DocumentPreview.tsx: -------------------------------------------------------------------------------- 1 | import { PaperClipIcon } from "@heroicons/react/solid"; 2 | import { validateYupSchema } from "formik"; 3 | import DocumentHeading from "./DocumentHeading" 4 | 5 | type CurrentContract = { 6 | [key: string]: Contracts; 7 | }; 8 | type Contracts = { 9 | [key: string]: string; 10 | }; 11 | interface PreviewProps { 12 | reqKeys: string[]; 13 | currentContract: CurrentContract; 14 | } 15 | 16 | const DocumentPreview: React.FC = ({ 17 | currentContract, 18 | reqKeys, 19 | }): JSX.Element => { 20 | 21 | 22 | /** BUILD STRUCTURE OF DOCUMENT PREVIEW 23 | * { endpoint1: [[{key: value}, {key: value}], [{key: value}]], endpoint2:...} 24 | */ 25 | const buildPreview = (contract, reqs) => { 26 | const endpoints = {}; 27 | // Loop through reqKeys of one endpoint 28 | for (let req of reqs) { 29 | const endpoint = []; 30 | const reqPairs = []; 31 | const resPairs = []; 32 | let reqKeys = contract[req]; 33 | for (let key in reqKeys) { 34 | let pair = {}; 35 | pair[key] = reqKeys[key]; 36 | reqPairs.push(pair); 37 | } 38 | endpoint.push(reqPairs); 39 | let res = "Res" + req.slice(3); 40 | let resKeys = contract[res]; 41 | for (let key in resKeys) { 42 | let pair = {}; 43 | pair[key] = resKeys[key]; 44 | resPairs.push(pair); 45 | // isn't it already an object? 46 | // we are looping through the keys in the object ah right 47 | } 48 | endpoint.push(resPairs); 49 | endpoints[req.slice(4)] = endpoint; 50 | } 51 | return endpoints; 52 | }; 53 | 54 | const docObj = buildPreview(currentContract, reqKeys); 55 | 56 | // 57 | const documentation = []; 58 | /** BUILD DOCUMENTATION ARRAY ITERATING THROUGH PREVIEW STRUCTURE 59 | * NOTE: VERY POSSIBLE TO COMBINE THIS WITH BUILD PREVIEW FUNCTION FOR LESS CODE 60 | */ 61 | // let keyIndex = 0; 62 | for (let key in docObj) { 63 | let splitKey = key.split("@"); 64 | const requests = []; 65 | const responses = []; 66 | // for (let request of docObj[key][0]) { 67 | // requests.push( 68 | //
69 | //
70 | // {Object.keys(request)[0]} 71 | //
72 | //
73 | // {Object.values(request)[0]} 74 | //
75 | //

76 | //
77 | // ); 78 | // } 79 | for (let i = 0; i < docObj[key][0].length; i++) { 80 | let request = docObj[key][0][i]; 81 | requests.push( 82 |
83 |
84 | {Object.keys(request)[0]} 85 |
86 |
87 | {Object.values(request)[0]} 88 |
89 |

90 |
91 | ); 92 | } 93 | for (let i = 0; i < docObj[key][1].length; i++) { 94 | let response = docObj[key][1][i]; 95 | responses.push( 96 |
97 |
98 | {Object.keys(response)[0]} 99 |
100 |
101 | {Object.values(response)[0]} 102 |
103 |

104 |
105 | ); 106 | } 107 | documentation.push( 108 |
112 |
113 |

114 | {splitKey[1]} 115 |

116 |

{splitKey[0]}

117 |
118 |
119 |
120 |
121 |
122 | Request Type: Object 123 |
124 | {/*
Object
*/} 125 |
{requests}
126 |
127 | 128 |
129 |
130 | Response Type: Object 131 |
132 | {/*
Object
*/} 133 |
{responses}
134 |
135 | 136 | {/*
137 |
Usage
138 |
139 | Fugiat ipsum ipsum deserunt culpa aute sint do nostrud anim 140 | incididunt cillum culpa consequat. Excepteur qui ipsum aliquip 141 | consequat sint. Sit id mollit nulla mollit nostrud in ea officia 142 | proident. Irure nostrud pariatur mollit ad adipisicing 143 | reprehenderit deserunt qui eu. 144 |
145 |
*/} 146 |
147 |
148 |
149 | ); 150 | } 151 | 152 | return
{documentation}
; 153 | }; 154 | 155 | export default DocumentPreview; 156 | -------------------------------------------------------------------------------- /src/components/FrontLog.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { RootState } from "../state/store"; 4 | import { updateLog } from "../state/features/frontLogSlice"; 5 | 6 | const socket = new WebSocket("ws://localhost:1234"); 7 | 8 | socket.addEventListener("open", (event) => { 9 | // console.log("CONNECTED TO WEB SOCKET FROM CLIENT Side"); 10 | }); 11 | 12 | export default function FrontLog() { 13 | const [requests, updateRequests] = useState([]); 14 | const currentLog = useSelector((store: RootState) => store.frontLog); 15 | const dispatch = useDispatch(); 16 | // useEffect(() => { }, []); 17 | 18 | socket.onmessage = (event) => { 19 | // logic to display received data here 20 | // likely use state components 21 | // console.log("MESSAGE RECEIVED FROM 1234: ", event.data); 22 | dispatch(updateLog(JSON.parse(event.data))); 23 | updateRequests([...requests, JSON.parse(event.data)]); 24 | }; 25 | 26 | // const sendMessage = () => { 27 | // socket.send('1. CLIENT 1 JUST SEND THIS MESSAGE TO SERVER!!!!'); 28 | // }; 29 | return ( 30 |
31 |
32 |
33 |
34 |

35 | Request Log 36 |

37 |
38 | {/* */} 41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | 50 | 51 | 52 | 58 | 64 | 70 | 76 | {/* */} 82 | 83 | 84 | 85 | {currentLog.map((request, index) => { 86 | const details = []; 87 | for (let i = 0; i < request.error.length; i++) { 88 | details.push(
{request.error[i]}

) 89 | } 90 | let reqStatus; 91 | if (request.pass === true) { 92 | reqStatus = ( 93 | 94 | Success 95 | 96 | ); 97 | } else if (request.pass === false) { 98 | reqStatus = ( 99 | 100 | Error 101 | 102 | ); 103 | } 104 | return ( 105 | 106 | 118 | 121 | 124 | 128 | 129 | ); 130 | })} 131 | 132 |
56 | Endpoint 57 | 62 | Status 63 | 68 | Time 69 | 74 | Report 75 | 80 | Edit 81 |
107 |
108 |
109 |
110 | {request.endpoint} 111 |
112 |
113 | {request.method} 114 |
115 |
116 |
117 |
119 | {reqStatus} 120 | 122 | {request.time} 123 | 125 | {/* {request.error} */} 126 | {details} 127 |
133 |
134 |
135 |
136 |
137 |
138 | ); 139 | } 140 | -------------------------------------------------------------------------------- /src/components/FrontTestBanner.tsx: -------------------------------------------------------------------------------- 1 | /* This example requires Tailwind CSS v2.0+ */ 2 | import { useState } from "react"; 3 | import { 4 | SpeakerphoneIcon, 5 | XIcon, 6 | SwitchHorizontalIcon, 7 | } from "@heroicons/react/outline"; 8 | const { shell } = require("electron"); 9 | 10 | const FrontTestBanner = () => { 11 | const [hidden, setHidden] = useState(false); 12 | return ( 13 | 72 | ); 73 | }; 74 | 75 | export default FrontTestBanner; 76 | -------------------------------------------------------------------------------- /src/components/Login.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link, Navigate, useNavigate } from 'react-router-dom'; 3 | import { useFormik } from 'formik'; 4 | import * as Yup from 'yup'; 5 | import axios from 'axios'; 6 | import { useDispatch} from 'react-redux' 7 | import { getUserData } from '../state/features/contractSlice'; 8 | 9 | type FormValues = { 10 | email: string; 11 | password: string; 12 | }; 13 | 14 | const initialValues: FormValues = { 15 | email: '', 16 | password: '', 17 | }; 18 | 19 | const Login = () => { 20 | const navigate = useNavigate(); 21 | const dispatch = useDispatch(); 22 | 23 | // formik.handleSubmit: prevents default and invokes the onSubmit function in formik object 24 | 25 | // formik.handleChange: updates the state of our values in formik object based on the value attribute of the html element 26 | 27 | // formik.handleBlur changes boolean value from false to true when the field is interacted with 28 | 29 | const formik = useFormik({ 30 | initialValues, 31 | validationSchema: Yup.object({ 32 | email: Yup.string().email('Invalid email format').required('Required'), 33 | password: Yup.string().required('invalid password'), 34 | }), 35 | onSubmit: (values: FormValues): void => { 36 | axios 37 | .post('http://localhost:4321/login', { 38 | password: values.password, 39 | email: values.email, 40 | }) 41 | .then((response) => { 42 | if (response.status === 200) { 43 | navigate("navbar"); 44 | dispatch(getUserData(response.data)); 45 | } 46 | }) 47 | .catch((error) => { 48 | console.log(error); 49 | }); 50 | }, 51 | }); 52 | 53 | return ( 54 | <> 55 |
56 |
57 | Contractual 62 |

63 | Login 64 |

65 |
66 | or{' '} 67 | 68 |

69 | Register for an account 70 |

71 | 72 |
73 |
74 | 75 |
76 |
77 |
78 |
79 | 85 |
86 | 98 |
99 | {/* {formik.touched.email && formik.errors.email ? (

{formik.errors.email}

) : (

 

)} */} 100 |
101 | 102 |
103 | 109 |
110 | 122 |
123 | {/* {formik.touched.password && formik.errors.password ? (

{formik.errors.password}

) : (

 

)} */} 124 |
125 | 126 |
127 | 133 |
134 |
135 |
136 |
137 |
138 | 139 | ); 140 | }; 141 | 142 | export default Login; 143 | -------------------------------------------------------------------------------- /src/components/ModalContractDetails.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment, useState } from 'react' 2 | import { Dialog, Transition } from '@headlessui/react' 3 | import { DocumentTextIcon } from '@heroicons/react/outline' 4 | 5 | interface ModalProps { 6 | visibility: boolean, 7 | closeModal: () => void, 8 | tokens: Tokens, 9 | currentContractToken: string 10 | } 11 | type Tokens = { 12 | [key: string]: string 13 | } 14 | const ModalContractDetails: React.FC = ({ 15 | visibility, 16 | closeModal, 17 | tokens, 18 | currentContractToken 19 | }) => { 20 | const [open, setOpen] = useState(true) 21 | 22 | let activeContract = ''; 23 | for (let token in tokens) { 24 | if (tokens[token] === currentContractToken) activeContract = token 25 | } 26 | return ( 27 | 28 | 29 |
30 | 39 | 40 | 41 | 42 | {/* This element is to trick the browser into centering the modal contents. */} 43 | 46 | 55 |
56 |
57 |
58 |
60 |
61 | 62 | Contract: {activeContract} 63 | 64 |
65 |

66 | Token: {currentContractToken} 67 |

68 |
69 |
70 |
71 |
72 | 79 |
80 |
81 |
82 |
83 |
84 |
85 | ) 86 | } 87 | 88 | export default ModalContractDetails; -------------------------------------------------------------------------------- /src/components/ModalJoinContract.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment, useRef, useState } from 'react' 2 | import { Dialog, Transition } from '@headlessui/react' 3 | import { CheckIcon, PlusIcon } from '@heroicons/react/outline' 4 | import axios from 'axios' 5 | import { useDispatch, useSelector } from 'react-redux' 6 | import { RootState } from '../state/store'; 7 | import { joinContract } from '../state/features/contractSlice'; 8 | // import ModalNotification from './ModalNotification'; 9 | 10 | interface ModalProps { 11 | visibility: boolean, 12 | closeModal: () => void, 13 | setSelectedContract: (contract: EnumContractItem) => void, 14 | sendToken: (token: string) => void 15 | } 16 | interface EnumContractItem { 17 | token: string; 18 | name: string; 19 | } 20 | const ModalJoinContract: React.FC = ({ 21 | visibility, 22 | closeModal, 23 | setSelectedContract, 24 | sendToken 25 | }): JSX.Element => { 26 | const dispatch = useDispatch() 27 | const [contractName, setContractName] = useState('') 28 | const [contractToken, setContractToken] = useState('') 29 | const { userId } = useSelector((store: RootState) => store.contract); 30 | 31 | const cancelButtonRef = useRef(null) 32 | 33 | /** ADD CONTRACT THAT WAS PREVIOUSLY BUILT BY SEPARATE USER VIA CONTRACT NAME AND TOKEN INPUT */ 34 | const handleJoinContract = (): void => { 35 | axios 36 | .post('http://localhost:4321/contract/details', { 37 | import: true, 38 | userId, 39 | name: contractName, 40 | token: contractToken 41 | }) 42 | .then((response) => { 43 | if (response.status === 200) { 44 | dispatch(joinContract({ 45 | name: contractName, 46 | token: contractToken, 47 | contract: response.data.content 48 | })); 49 | setSelectedContract({name: contractName, token: contractToken}); 50 | /** UPDATE WEBHOOK SERVER HERE */ 51 | sendToken(contractToken) 52 | } 53 | }) 54 | .catch((error) => { 55 | console.log(error); 56 | }); 57 | }; 58 | return ( 59 | 60 | {/* */} 61 | 62 |
63 | 72 | 73 | 74 | 75 | {/* This element is to trick the browser into centering the modal contents. */} 76 | 79 | 88 |
89 |
90 |
91 |
93 |
94 | 95 | Join existing data contract 96 | 97 |
98 |

99 | Provide contract name and token 100 |

101 |
102 |
103 |
104 |
105 |
106 | setContractName(e.target.value)} 112 | className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" 113 | placeholder="Contract name" 114 | /> 115 |
116 |
117 | setContractToken(e.target.value)} 123 | className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" 124 | placeholder="Token" 125 | /> 126 |
127 |
128 |
129 | 136 | 144 |
145 |
146 |
147 |
148 |
149 |
150 | ) 151 | } 152 | 153 | export default ModalJoinContract; -------------------------------------------------------------------------------- /src/components/ModalNewContract.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment, useRef, useState } from "react"; 2 | import { Dialog, Transition } from "@headlessui/react"; 3 | import { CheckIcon, SparklesIcon } from "@heroicons/react/outline"; 4 | import axios from "axios"; 5 | import { useDispatch, useSelector } from "react-redux"; 6 | import { RootState } from "../state/store"; 7 | import { addContract } from "../state/features/contractSlice"; 8 | import { showNotification } from "../state/features/modalsSlice"; 9 | 10 | interface ModalProps { 11 | visibility: boolean; 12 | closeModal: () => void; 13 | setSelectedContract: (contract: EnumContractItem) => void; 14 | sendToken: (token: string) => void; 15 | } 16 | interface EnumContractItem { 17 | token: string; 18 | name: string; 19 | } 20 | const ModalNewContract: React.FC = ({ 21 | visibility, 22 | closeModal, 23 | setSelectedContract, 24 | sendToken, 25 | }): JSX.Element => { 26 | const dispatch = useDispatch(); 27 | const [contractName, setContractName] = useState(""); 28 | const cancelButtonRef = useRef(null); 29 | const { userId } = useSelector((store: RootState) => store.contract); 30 | 31 | /** CREATE A NEW CONTRACT AND RECEIVE UNIQUE TOKEN */ 32 | const createContract = (): void => { 33 | axios 34 | .post("http://localhost:4321/contract/add", { 35 | title: contractName, 36 | userId: userId, 37 | }) 38 | .then((response) => { 39 | if (response.status === 200) { 40 | 41 | dispatch( 42 | addContract({ 43 | name: contractName, 44 | token: response.data, 45 | }) 46 | ); 47 | setSelectedContract({ name: contractName, token: response.data }); 48 | sendToken(response.data); 49 | } 50 | }) 51 | .catch((error) => { 52 | dispatch(showNotification(true)); 53 | console.log(error); 54 | }); 55 | }; 56 | 57 | /** STOP MODAL FROM CLOSING IF CONTRACT NAME ALREADY EXISTS 58 | * INCOMPLETE 59 | */ 60 | // const handleClickCreate = () => { } 61 | return ( 62 | 63 | 69 |
70 | 79 | 80 | 81 | 82 | {/* This element is to trick the browser into centering the modal contents. */} 83 | 89 | 98 |
99 |
100 |
101 |
106 |
107 | 111 | Add new data contract 112 | 113 |
114 |

115 | Choose a descriptive name for your new contract. 116 |

117 |

118 | Contract name must be unique or it will not be created. 119 |

120 |
121 |
122 |
123 |
124 |
125 | setContractName(e.target.value)} 131 | className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" 132 | placeholder="" 133 | /> 134 |
135 |
136 |
137 | 147 | 155 |
156 |
157 |
158 |
159 |
160 |
161 | ); 162 | }; 163 | 164 | export default ModalNewContract; 165 | -------------------------------------------------------------------------------- /src/components/Notification.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment, useState } from 'react' 2 | import { Transition } from '@headlessui/react' 3 | import { CheckCircleIcon, XCircleIcon } from '@heroicons/react/outline' 4 | import { XIcon } from '@heroicons/react/solid' 5 | 6 | interface NotificationProps { 7 | notificationString: string; 8 | visibility: boolean; 9 | setVisibility: (input: boolean) => void; 10 | positiveFeedback: boolean; 11 | } 12 | 13 | const Notification: React.FC = ({ 14 | notificationString, 15 | visibility, 16 | setVisibility, 17 | positiveFeedback, 18 | }) => { 19 | // const [show, setShow] = useState(true) 20 | 21 | return ( 22 | <> 23 | {/* Global notification live region, render this permanently at the end of the document */} 24 |
28 |
29 | {/* Notification panel, dynamically insert this into the live region when it needs to be displayed */} 30 | 41 |
42 |
43 |
44 | { 45 | positiveFeedback ?
46 |
:
48 |
50 | } 51 | 52 | {/*
53 |
55 |
56 |
*/} 58 | 59 |
60 |

{notificationString}

61 | {/*

Some more details here.

*/} 62 |
63 |
64 | 74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | 82 | ) 83 | } 84 | 85 | export default Notification; -------------------------------------------------------------------------------- /src/components/Register.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { Link, Navigate, useNavigate } from 'react-router-dom'; 3 | import { useFormik } from 'formik'; 4 | import * as Yup from 'yup'; 5 | import axios from 'axios'; 6 | import { useDispatch} from 'react-redux' 7 | import { getUserData } from '../state/features/contractSlice'; 8 | 9 | type FormValues = { 10 | name: string; 11 | email: string; 12 | password: string; 13 | }; 14 | 15 | const initialValues: FormValues = { 16 | name: '', 17 | email: '', 18 | password: '', 19 | }; 20 | 21 | const Register = () => { 22 | const navigate = useNavigate(); 23 | const dispatch = useDispatch(); 24 | 25 | // formik.handleSubmit: prevents default and invokes the onSubmit function in formik object 26 | 27 | // formik.handleChange: updates the state of our values in formik object based on the value attribute of the html element 28 | 29 | // formik.handleBlur changes boolean value from false to true when the field is interacted with 30 | 31 | const formik = useFormik({ 32 | initialValues, 33 | validationSchema: Yup.object({ 34 | name: Yup.string() 35 | .max(15, 'Must be 15 characters or less') 36 | .required('Required'), 37 | email: Yup.string().email('Invalid email format').required('Required'), 38 | password: Yup.string().required('invalid password'), 39 | }), 40 | onSubmit: (values: FormValues): void => { 41 | axios 42 | .post('http://localhost:4321/register', { 43 | name: values.name, 44 | email: values.email, 45 | password: values.password, 46 | }) 47 | .then((response) => { 48 | navigate('../navbar'); 49 | dispatch(getUserData(response.data)); 50 | }) 51 | .catch((error) => { 52 | console.log(error); 53 | navigate('../'); 54 | }); 55 | }, 56 | }); 57 | 58 | return ( 59 | <> 60 |
61 |
62 | Workflow 67 |

68 | Register 69 |

70 |
71 | or{' '} 72 | 73 |

74 | Login to your account 75 |

76 | 77 |
78 |
79 | 80 |
81 |
82 |
83 |
84 | 90 |
91 | 103 |
104 | {formik.touched.name && formik.errors.name ? (

{formik.errors.name}

) : (

 

)} 105 |
106 | 107 |
108 | 114 |
115 | 127 |
128 | {formik.touched.email && formik.errors.email ? (

{formik.errors.email}

) : (

 

)} 129 |
130 | 131 |
132 | 138 |
139 | 151 |
152 | {formik.touched.password && formik.errors.password ? (

{formik.errors.password}

) : (

 

)} 153 |
154 | 155 |
156 | 162 |
163 |
164 |
165 |
166 |
167 | 168 | ); 169 | }; 170 | 171 | export default Register; 172 | -------------------------------------------------------------------------------- /src/components/modalNotification.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment, useState } from "react"; 2 | import { Transition } from "@headlessui/react"; 3 | import { CheckCircleIcon, XCircleIcon } from "@heroicons/react/outline"; 4 | import { XIcon } from "@heroicons/react/solid"; 5 | import { useDispatch, useSelector } from 'react-redux'; 6 | import { RootState } from '../state/store'; 7 | import { showNotification } from '../state/features/modalsSlice'; 8 | 9 | interface ModalNotificationProps { 10 | // className: string; 11 | } 12 | 13 | const ModalNotification: React.FC = () => { 14 | const dispatch = useDispatch() 15 | const { modalNotification } = useSelector((store: RootState) => store.modals); 16 | 17 | const closeNotification = () => { 18 | dispatch(showNotification(false)) 19 | } 20 | return ( 21 | <> 22 | {/* Global notification live region, render this permanently at the end of the document */} 23 |
27 |
28 | {/* Notification panel, dynamically insert this into the live region when it needs to be displayed */} 29 | 39 |
40 |
41 |
42 |
43 |
45 |
46 |

47 | CONTRACT NAME IS NOT UNIQUE. 48 |

49 |

50 | PLEASE TRY A DIFFERENT NAME 51 |

52 |
53 |
54 | 61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | 69 | ); 70 | }; 71 | 72 | export default ModalNotification; 73 | -------------------------------------------------------------------------------- /src/containers/BackTester.tsx: -------------------------------------------------------------------------------- 1 | import BackEndpoint from "../components/BackEndpoint"; 2 | import BackRequestEditor from "../components/BackRequestEditor"; 3 | import BackLog from "../components/BackLog"; 4 | import { useState } from "react"; 5 | import { useSelector } from 'react-redux'; 6 | import { RootState } from '../state/store'; 7 | import { checkInput } from "../express/testing_server/controllers/contractOp"; 8 | 9 | interface EnumEndpointItem { 10 | id: number; 11 | method: string; 12 | name: string; 13 | } 14 | type CurrentContract = { 15 | [key: string]: Contracts 16 | } 17 | type Contracts = { 18 | [key: string]: string 19 | } 20 | type KeyTypeValue = { 21 | reqKey: string; 22 | reqValType: string; 23 | reqVal: string | number | boolean | any[]; 24 | }; 25 | type BodyInputs = KeyTypeValue[]; 26 | 27 | export default function BackTester() { 28 | const [reqMethod, setReqMethod] = useState("GET"); 29 | const [endpoint, setEndpoint] = useState(""); 30 | const [URLString, setURLString] = useState(""); 31 | const [reqInputs, setReqInputs] = useState([ 32 | { reqKey: "", reqValType: "boolean", reqVal: "true" }, 33 | ]); 34 | const { currentContract } = useSelector((state: RootState) => state.contract); 35 | 36 | /** CREATE ENDPOINTS OBJECT ARRAY FOR ENUM IN BACKENDPOINT COMPONENT */ 37 | const getEndpoints = (contract: CurrentContract ):EnumEndpointItem[] => { 38 | const endpoints = []; 39 | let id = 1; 40 | for (let key in contract) { 41 | if (key.slice(0,3) === 'Req') { 42 | const endpoint = key.split('@')[2]; 43 | const method = key.split('@')[1]; 44 | endpoints.push({ id, method, name: endpoint }); 45 | id++; 46 | } 47 | } 48 | return endpoints 49 | // return reqKeys; 50 | // console.log(getReqKeys(currentContract)); 51 | }; 52 | const reqEndpoints: EnumEndpointItem[] = getEndpoints(currentContract); 53 | 54 | /** RECORD CHANGES TO REQ TYPE DROPDOWN IN BACKENDPOINT COMPONENT 55 | * NOTE: DROPDOWN CURRENTLY CHANGED TO DISPLAY ONLY FOR TESTING. 56 | * DEVELOPER DECISION TO REMOVE 57 | */ 58 | const handleSetReqMethod = (e: string): void => { 59 | // const method: string = e.target.value; 60 | // console.log("method changed: ", method); 61 | setReqMethod(e); 62 | }; 63 | 64 | /** RECORD STATE CHANGES IN DOMAIN INPUT FIELD IN BACKENDPOINT COMPONENT */ 65 | const handleSetURL = (e: any): void => { 66 | const URLString: string = e.target.value; 67 | setURLString(URLString); 68 | }; 69 | 70 | /** RECORD INPUTS OF KEY/TYPE/VALUE TRIOS IN THE REQUEST BODY SECTION IN COMPONENT LEVEL STATE */ 71 | const handleSetReqInputs = (index, e) => { 72 | let data = [...reqInputs]; 73 | data[index][e.target.name] = e.target.value; 74 | 75 | 76 | /// TESTING 77 | 78 | 79 | if (data[index][e.target.name] === 'boolean') { 80 | data[index].reqVal = true 81 | } 82 | else if (data[index][e.target.name] === 'string') { 83 | data[index].reqVal = '' 84 | } 85 | else if (data[index][e.target.name] === 'number') { 86 | data[index].reqVal = '' 87 | } 88 | else if (data[index][e.target.name] === 'array-any-any') { 89 | data[index].reqVal = '' 90 | } 91 | 92 | /// END OF TEST 93 | setReqInputs(data); 94 | }; 95 | 96 | /** CHANGE THE DEFAULT VALUE OF VALUE FIELD WHEN MODIFYING THE DATATYPE DROPDOWN */ 97 | const updateDefaultValue = (index, e) => { 98 | let data = [...reqInputs] 99 | } 100 | /** UPDATE CURRENT INPUT FIELDS STATE VARIABLES BASED ON COMBOBOX DROPDOWN ENUM SELECTION */ 101 | const updateReqFields = (reqEndpointKey: string):void => { 102 | const endpointKeys: Contracts = currentContract[reqEndpointKey] 103 | let keys = []; 104 | for (let key in endpointKeys) { 105 | const k = {reqKey: key, reqValType: endpointKeys[key], reqVal: ''}; 106 | if (k.reqValType === 'boolean') k.reqVal = 'true'; 107 | else if (k.reqValType === 'array-any-any') k.reqVal = '' 108 | keys.push(k) 109 | } 110 | setReqInputs(keys); 111 | }; 112 | 113 | 114 | return ( 115 |
116 | 128 | 134 | 135 |
136 | ); 137 | } 138 | -------------------------------------------------------------------------------- /src/containers/ContractBuilder.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import ContractEndpoint from '../components/ContractEndpoint'; 3 | import ContractEditor from '../components/ContractEditor'; 4 | import Notification from '../components/Notification'; 5 | import { useSelector } from 'react-redux'; 6 | import { RootState } from '../state/store'; 7 | 8 | interface EnumEndpointItem { 9 | id: number; 10 | method: string; 11 | name: string; 12 | } 13 | type CurrentContract = { 14 | [key: string]: Contracts 15 | } 16 | type Contracts = { 17 | [key: string]: string 18 | } 19 | 20 | type KeyAndType = { 21 | [key: string]: string; 22 | }; 23 | type BodyInputs = KeyAndType[]; 24 | 25 | export default function ContractBuilder() { 26 | const [reqMethod, setReqMethod] = useState('GET'); 27 | const [newEndpoint, setNewEndpoint] = useState(''); 28 | const [reqInputs, setReqInputs] = useState([{ reqKey: '', reqValType: 'boolean' }]) 29 | const [resInputs, setResInputs] = useState([{ resKey: '', resValType: 'boolean' }]) 30 | const [notificationString, setNotificationString] = useState('') 31 | const [notificationOpen, setNotificationOpen] = useState(false) 32 | const [positiveFeedback, setPositiveFeedback] = useState(false) 33 | const { currentContract } = useSelector((state: RootState) => state.contract); 34 | 35 | /** RECORD CHANGES TO REQ TYPE DROPDOWN IN CONTRACTENDPOINT COMPONENT */ 36 | const handleSetReqMethod = (e: any): void => { 37 | const method: string = e.target.value; 38 | setReqMethod(method); 39 | }; 40 | 41 | /** RECORD CHANGES IN ENDPOINT INPUT FIELD IN CONTRACTENDPOINT COMPONENT */ 42 | const handleSetEndpoint = (e: any): void => { 43 | const endpoint: string = e.target.value; 44 | setNewEndpoint(endpoint); 45 | }; 46 | 47 | /** RECORD INPUTS OF KEY/TYPE PAIRS IN THE REQUEST BODY SECTION IN COMPONENT LEVEL STATE */ 48 | const handleSetReqInputs = (index, e) => { 49 | let data = [...reqInputs]; 50 | data[index][e.target.name] = e.target.value; 51 | setReqInputs(data); 52 | }; 53 | 54 | /** RECORD INPUTS OF KEY/TYPE PAIRS IN THE RESPONSE BODY SECTION IN COMPONENT LEVEL STATE */ 55 | const handleSetResInputs = (index, e) => { 56 | let data = [...resInputs]; 57 | data[index][e.target.name] = e.target.value; 58 | setResInputs(data); 59 | }; 60 | 61 | /** ADD AN ADDITIONAL KEY/TYPE PAIR FIELD IN THE REQUEST BODY SECTION */ 62 | const addReqField = () => { 63 | let additional = { reqKey: '', reqValType: 'boolean' }; 64 | setReqInputs([...reqInputs, additional]); 65 | }; 66 | 67 | /** REMOVE LAST KEY/TYPE PAIR FIELD IN THE REQUEST BODY SECTION */ 68 | const subtractReqField = (e) => { 69 | e.preventDefault() 70 | const newReqInputs = JSON.parse(JSON.stringify(reqInputs)); 71 | setReqInputs(newReqInputs.slice(0,-1)); 72 | }; 73 | 74 | /** ADD AN ADDITIONAL KEY/TYPE PAIR FIELD IN THE RESPONSE BODY SECTION */ 75 | const addResField = () => { 76 | let additional = { resKey: '', resValType: 'boolean' }; 77 | setResInputs([...resInputs, additional]); 78 | }; 79 | 80 | /** REMOVE LAST KEY/TYPE PAIR FIELD IN THE REQUEST BODY SECTION */ 81 | const subtractResField = (e) => { 82 | e.preventDefault() 83 | const newResInputs = JSON.parse(JSON.stringify(resInputs)); 84 | setResInputs(newResInputs.slice(0,-1)); 85 | }; 86 | 87 | /** RESET ALL CURRENT REQUEST AND RESPONSE KEY/TYPE PAIRS TO INITIAL STATE 88 | NOTE: may want to add resetting the dropdown and enpoint input fiels as well 89 | */ 90 | const resetFields = () => { 91 | // setNewEndpoint(''); 92 | setReqInputs([{ reqKey: '', reqValType: 'boolean' }]) 93 | setResInputs([{ resKey: '', resValType: 'boolean' }]) 94 | } 95 | 96 | /** CREATE ENDPOINTS OBJECT ARRAY FOR ENUM IN CONTRACTENDPOINT COMPONENT */ 97 | const getEndpoints = (contract: CurrentContract ):EnumEndpointItem[] => { 98 | const endpoints = []; 99 | let id = 1; 100 | for (let key in contract) { 101 | if (key.slice(0,3) === 'Req') { 102 | const endpoint = key.split('@')[2]; 103 | const method = key.split('@')[1]; 104 | endpoints.push({ id, method, name: endpoint }); 105 | id++; 106 | } 107 | } 108 | return endpoints 109 | }; 110 | 111 | /** UPDATE CURRENT CONTRACT INPUT FIELD STATE VARIABLES BASED ON COMBOBOX DROPDOWN ENUM SELECTION */ 112 | const updateFieldsByEndpoint = (reqEndpointKey: string, resEndpointKey: string):void => { 113 | const reqEndpointKeys: Contracts = currentContract[reqEndpointKey] 114 | const resEndpointKeys: Contracts = currentContract[resEndpointKey] 115 | let reqKeys = []; 116 | let resKeys = []; 117 | for (let key in reqEndpointKeys) { 118 | const k = {reqKey: key, reqValType: reqEndpointKeys[key]}; 119 | reqKeys.push(k) 120 | } 121 | for (let key in resEndpointKeys) { 122 | const k = {resKey: key, resValType: resEndpointKeys[key]}; 123 | resKeys.push(k) 124 | } 125 | setReqInputs(reqKeys); 126 | setResInputs(resKeys); 127 | }; 128 | 129 | /** BUILD ENUM ARRAY */ 130 | const reqEndpoints: EnumEndpointItem[] = getEndpoints(currentContract); 131 | return ( 132 |
133 | 139 | 155 | 165 |
166 | ); 167 | } 168 | -------------------------------------------------------------------------------- /src/containers/CounterContainer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useDispatch, useSelector } from 'react-redux'; 3 | import { RootState } from '../state/store'; 4 | import { increment, decrement } from '../state/features/counterSlice'; 5 | 6 | export default function CounterContainer() { 7 | // non-destructured version 8 | // const counter = useSelector((state: RootState) => state.counter.counter); 9 | const { counter } = useSelector((state: RootState) => state.counter); 10 | 11 | const dispatch = useDispatch(); 12 | 13 | // can pass these functions into button instead if desired 14 | // const incrementCounter = () => dispatch(increment(counter)); 15 | // const decrementCounter = () => dispatch(decrement(counter)); 16 | 17 | return ( 18 |
19 |

{counter}

20 | 23 | 26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/containers/DocumentCreator.tsx: -------------------------------------------------------------------------------- 1 | import DocumentExport from "../components/DocumentExport"; 2 | import DocumentPreview from "../components/DocumentPreview"; 3 | // TEST 4 | import DocumentHeading from "../components/DocumentHeading"; 5 | // TEST END 6 | import { useState } from 'react'; 7 | import { useSelector } from "react-redux"; 8 | import { RootState } from "../state/store"; 9 | 10 | import React from "react"; 11 | import html2canvas from "html2canvas"; 12 | import { jsPDF } from "jspdf"; 13 | 14 | const DocumentCreator = () => { 15 | const [fileName, setFileName] = useState('') 16 | const { currentContract } = useSelector((state: RootState) => state.contract); 17 | 18 | /** BUILD ARRAY OF REQUESTMETHOD AND ENPOINTS FOR CURRENT SELECTED CONTRACT */ 19 | const getReqKeys = (contract) => { 20 | const reqs = []; 21 | for (let key in contract) { 22 | if (key.slice(0, 3) === "Req") { 23 | reqs.push(key); 24 | } 25 | } 26 | return reqs; 27 | }; 28 | const reqKeys = getReqKeys(currentContract); 29 | // console.log(reqKeys); 30 | 31 | const printRef = React.useRef(); 32 | 33 | const handleDownloadPdf = async () => { 34 | const element = printRef.current; 35 | const canvas = await html2canvas(element); 36 | const data = canvas.toDataURL("image/png"); 37 | 38 | const pdf = new jsPDF(); 39 | const imgProperties = pdf.getImageProperties(data); 40 | const pdfWidth = pdf.internal.pageSize.getWidth(); 41 | const pdfHeight = (imgProperties.height * pdfWidth) / imgProperties.width; 42 | 43 | pdf.addImage(data, "PNG", 0, 0, pdfWidth, pdfHeight); 44 | pdf.save(`${fileName}.pdf`); 45 | }; 46 | 47 | return ( 48 |
49 | 54 |
55 | 56 | 60 |
61 |
62 | ); 63 | }; 64 | 65 | export default DocumentCreator; 66 | -------------------------------------------------------------------------------- /src/containers/FrontTester.tsx: -------------------------------------------------------------------------------- 1 | import FrontLog from '../components/FrontLog'; 2 | import FrontTestBanner from '../components/FrontTestBanner'; 3 | import { useState, useEffect } from 'react'; 4 | import { RootState } from '../state/store'; 5 | import { useSelector } from 'react-redux'; 6 | import axios from 'axios'; 7 | 8 | export default function FrontTester() { 9 | const { currentContractToken } = useSelector((store: RootState) => store.contract); 10 | // const sendToken = () => { 11 | // axios 12 | // .get(`http://localhost:1234/contract/${currentContractToken}`) 13 | // .then((response) => { 14 | // console.log(response) 15 | // }) 16 | // .catch((error) => { 17 | // console.log('Error is: ', error) 18 | // }) 19 | // } 20 | 21 | return ( 22 |
23 | {/* */} 24 | 25 | 26 | {/* 29 | {message.map((element, index) => { 30 | return ( 31 |
{element}
32 | )})} */} 33 |
34 | //
35 | //
36 | //
37 | //

Request Log:

38 | //
39 | //
40 | //

Edit Response for:

41 | //
POST /endpoint
42 | //
Headers
43 | //
Data-Structure
44 | //
45 | //
    46 | //
    47 | //
    48 | //
    49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /src/express/testing_server/controllers/checkController.js: -------------------------------------------------------------------------------- 1 | const { checkInput } = require("./contractOp"); 2 | 3 | const checkController = {}; 4 | 5 | function getTime() { 6 | const today = new Date(); 7 | const date = today.getMonth() + 1 + "/" + today.getDate(); 8 | const time = 9 | String(today.getHours()).padStart(2, "0") + 10 | ":" + 11 | String(today.getMinutes()).padStart(2, "0") + 12 | ":" + 13 | String(today.getSeconds()).padStart(2, "0"); 14 | return time + " [" + date + "]"; 15 | } 16 | 17 | checkController.checkReq = async (req, res, next) => { 18 | const endpoint = req.path; 19 | const method = req.method.toUpperCase(); 20 | const key = "Req@" + method + "@" + endpoint; 21 | 22 | const result = checkInput(req.body, currentContract, key); 23 | const report = {}; 24 | report["endpoint"] = endpoint; 25 | report["method"] = method; 26 | report["pass"] = result.pass; 27 | report["time"] = getTime(); 28 | report["error"] = result.error; 29 | res.locals.report = report; 30 | return next(); 31 | }; 32 | 33 | module.exports = checkController; 34 | -------------------------------------------------------------------------------- /src/express/testing_server/controllers/contractOp.js: -------------------------------------------------------------------------------- 1 | // ======== Contract to String ========== 2 | /* 3 | Frontend saves all the contracts for a given application in an object. 4 | 5 | In this object, each key-value pair is a contract. 6 | 7 | 1. The key is a string, containing the essential info about where this constract should be applied to. 8 | The key is in the format of "@@", e.g. "Req@POST@/login" 9 | 10 | 2. The value is about the content of this contract, which is an object stores all the name-type pairs. 11 | e.g. {username: "string", age: "number"} 12 | 13 | The final contracts saved in the state looks like : 14 | { 15 | "Req@POST@/login": {username: "string", age: "number"}, 16 | "Res@POST@/login": {success: "boolean"}, 17 | "Req@POST@/habits": {habitname: "string", target: "number"}, 18 | "Res@POST@/habits": {currentHabits: "array-any-any"} 19 | } 20 | To extract a specific contract from the whole contracts, just do `contracts["Res@POST@/habits"] ` 21 | */ 22 | 23 | function checkInput(input, contracts, condition) { 24 | /* 25 | @return value: 26 | { 27 | pass: true | false, 28 | error:[] 29 | } 30 | */ 31 | const typeCheck = { 32 | number: (x) => typeof x === "number", 33 | string: (x) => typeof x === "string", 34 | boolean: (x) => typeof x === "boolean", 35 | array: (x) => Array.isArray(x), 36 | object: (x) => typeof x === "object" && !Array.isArray(x) && x !== null, 37 | }; 38 | 39 | // input has to be an object 40 | if (!typeCheck["object"](input)) 41 | return { 42 | pass: false, 43 | error: ["The req/res should be an object, but it's not!"], 44 | }; 45 | 46 | // condition must have been in the contracts 47 | if (contracts[condition] == undefined) 48 | return { 49 | pass: false, 50 | error: ["Endpoint or FETCH method does not exist!"], 51 | }; 52 | 53 | const res = { 54 | pass: true, 55 | error: [], 56 | }; 57 | 58 | const contract = contracts[condition]; 59 | 60 | // check each field 61 | for (let key in contract) { 62 | // key name doesn't match, record this error and continue 63 | if (input[key] === undefined) { 64 | res.pass = false; 65 | res.error.push(`The key "${key}" not found in the request!`); 66 | continue; 67 | } 68 | // key name matches, then check the value type 69 | const targetType = contract[key]; 70 | const value = input[key]; 71 | // if is an array 72 | if (targetType.slice(0, 3) == "arr") { 73 | // make sure it is an array 74 | if (!Array.isArray(value)) { 75 | res.pass = false; 76 | res.error.push(`Type of "${key}" should be an array!`); 77 | continue; 78 | } 79 | [elementType, targetLength] = targetType.split("-").slice(1); 80 | // test element type 81 | if (elementType !== "any") { 82 | for (let el of value) { 83 | const match = typeCheck[elementType](el); 84 | if (!match) { 85 | res.pass = false; 86 | res.error.push( 87 | `Array elements for "${key}" should only contain "${elementType}"!` 88 | ); 89 | } 90 | break; 91 | } 92 | } 93 | // test length 94 | if (targetLength !== "any") { 95 | if (value.length != targetLength) { 96 | res.pass = false; 97 | res.error.push( 98 | `Array length for "${key}" should be ${targetLength}!` 99 | ); 100 | } 101 | } 102 | } 103 | // not an array 104 | else { 105 | const match = typeCheck[targetType](value); 106 | if (!match) { 107 | res.pass = false; 108 | res.error.push(`Type of "${key}" should be ${targetType}!`); 109 | } 110 | } 111 | } 112 | let isReq = condition[2] == "q"; 113 | const identifier = isReq ? "The request " : "The response "; 114 | 115 | if (res.pass) { 116 | res.error = [`${identifier}${JSON.stringify(input)} passed the check!\n`]; 117 | } else { 118 | res.error.unshift( 119 | `${identifier}${JSON.stringify(input)} failed the check!\n` 120 | ); 121 | } 122 | return res; 123 | } 124 | 125 | function contract2string(contract) { 126 | return JSON.stringify(contract); 127 | } 128 | 129 | function string2constract(str) { 130 | return JSON.parse(str); 131 | } 132 | 133 | module.exports = { contract2string, string2constract, checkInput }; 134 | -------------------------------------------------------------------------------- /src/express/testing_server/controllers/contractOp.test.js: -------------------------------------------------------------------------------- 1 | const { 2 | contract2string, 3 | string2constract, 4 | checkInput, 5 | } = require("./contractOp.js"); 6 | 7 | describe("checkInput function except array functionality test", () => { 8 | const contracts = { 9 | "Req@POST@/login": { username: "string", age: "number" }, 10 | "Res@POST@/login": { success: "boolean" }, 11 | "Req@POST@/habits": { habitname: "string", target: "number" }, 12 | "Res@POST@/habits": { currentHabits: "array" }, 13 | }; 14 | 15 | it("should return false if input is not an object", () => { 16 | const condition = "Res@POST@/habits"; 17 | expect(checkInput([1, 2], contracts, condition).pass).toBe(false); 18 | expect(checkInput(null, contracts, condition).pass).toBe(false); 19 | expect(checkInput(2, contracts, condition).pass).toBe(false); 20 | expect(checkInput("req", contracts, condition).pass).toBe(false); 21 | }); 22 | 23 | it("should return false if no such condition found in contract", () => { 24 | const input = { username: "Yankun", age: 26 }; 25 | expect(checkInput(input, contracts, "Res@GET@/habits").pass).toBe(false); 26 | expect(checkInput(input, contracts, "Res@GET@/habits").error[0]).toBe( 27 | "Endpoint or FETCH method does not exist!" 28 | ); 29 | }); 30 | 31 | it("should return false if the other types don't match", () => { 32 | const input = { username: "Yankun", age: "26" }; 33 | expect(checkInput(input, contracts, "Req@POST@/login").pass).toBe(false); 34 | expect(checkInput(input, contracts, "Req@POST@/login").error[0]).toBe( 35 | `Type of "age" should be a number!` 36 | ); 37 | }); 38 | 39 | it("should return true if the everything matches", () => { 40 | const input = { username: "Yankun", age: 26 }; 41 | expect(checkInput(input, contracts, "Req@POST@/login").pass).toBe(true); 42 | expect(checkInput(input, contracts, "Req@POST@/login").error.length).toBe( 43 | 0 44 | ); 45 | }); 46 | }); 47 | 48 | describe("checkInput function array functionality test", () => { 49 | const contracts = { 50 | "Res@POST@/res1": { calendar: "array-number-30", date: "string" }, 51 | "Res@POST@/res2": { currentHabits: "array-string-any", date: "string" }, 52 | "Res@POST@/res3": { books: "array-any-30", date: "string" }, 53 | "Res@POST@/res4": { currentHabits: "array-any-any", date: "string" }, 54 | }; 55 | 56 | it("should return false if input is not an array", () => { 57 | const condition = "Res@POST@/res1"; 58 | expect( 59 | checkInput({ calendar: "array", date: "0516" }, contracts, condition).pass 60 | ).toBe(false); 61 | expect( 62 | checkInput({ calendar: "array", date: "0516" }, contracts, condition) 63 | .error[0] 64 | ).toBe(`type of "calendar" do not match! It should be an array!`); 65 | }); 66 | 67 | describe("check element types", () => { 68 | it("should return false if element types don't match", () => { 69 | const condition = "Res@POST@/res1"; 70 | expect( 71 | checkInput( 72 | { calendar: Array(30).fill("a"), date: "0516" }, 73 | contracts, 74 | condition 75 | ).pass 76 | ).toBe(false); 77 | expect( 78 | checkInput( 79 | { calendar: Array(30).fill("a"), date: "0516" }, 80 | contracts, 81 | condition 82 | ).error[0] 83 | ).toBe( 84 | `type of array elements for "calendar" do not match! It should only contain "number"!` 85 | ); 86 | }); 87 | 88 | it("should return true if element type set to any", () => { 89 | const condition = "Res@POST@/res3"; 90 | expect( 91 | checkInput( 92 | { books: Array(30).fill(2), date: "0516" }, 93 | contracts, 94 | condition 95 | ).pass 96 | ).toBe(true); 97 | expect( 98 | checkInput( 99 | { books: Array(30).fill("a"), date: "0516" }, 100 | contracts, 101 | condition 102 | ).error.length 103 | ).toBe(0); 104 | }); 105 | }); 106 | 107 | describe("check array length", () => { 108 | it("should return false if array length don't match", () => { 109 | const condition = "Res@POST@/res3"; 110 | expect( 111 | checkInput( 112 | { books: Array(10).fill("a"), date: "0516" }, 113 | contracts, 114 | condition 115 | ).pass 116 | ).toBe(false); 117 | expect( 118 | checkInput( 119 | { books: Array(10).fill("a"), date: "0516" }, 120 | contracts, 121 | condition 122 | ).error[0] 123 | ).toBe(`array length for "books" do not match! It should be 30!`); 124 | expect( 125 | checkInput( 126 | { books: Array(10).fill("a"), date: "0516" }, 127 | contracts, 128 | condition 129 | ).error.length 130 | ).toBe(1); 131 | }); 132 | 133 | it("should return true if length set to any", () => { 134 | const condition = "Res@POST@/res4"; 135 | expect( 136 | checkInput( 137 | { currentHabits: Array(20).fill(2), date: "0516" }, 138 | contracts, 139 | condition 140 | ).pass 141 | ).toBe(true); 142 | expect( 143 | checkInput( 144 | { currentHabits: Array(20).fill(2), date: "0516" }, 145 | contracts, 146 | condition 147 | ).error.length 148 | ).toBe(0); 149 | }); 150 | }); 151 | }); 152 | -------------------------------------------------------------------------------- /src/express/testing_server/controllers/dbController.js: -------------------------------------------------------------------------------- 1 | const db = require("../models/dbModel.js"); 2 | 3 | const dbController = {}; 4 | 5 | dbController.getContent = async (req, res, next) => { 6 | const { token } = req.params; 7 | 8 | // convert to upper case 9 | const param = [token.toUpperCase()]; 10 | try { 11 | const getContent = ` 12 | SELECT * FROM contracts 13 | WHERE token = $1; 14 | `; 15 | 16 | const targetContent = await db.query(getContent, param); 17 | // targetContent returns a JSON object 18 | currentContract = JSON.parse(targetContent.rows[0].content); 19 | //console.log(currentContract); 20 | return next(); 21 | } catch (error) { 22 | return next({ 23 | log: `Express error in getContent middleware ${error}`, 24 | status: 400, 25 | message: { 26 | err: `dbController.getContent: ERROR: ${error}`, 27 | }, 28 | }); 29 | } 30 | }; 31 | 32 | module.exports = dbController; 33 | -------------------------------------------------------------------------------- /src/express/testing_server/controllers/mockController.js: -------------------------------------------------------------------------------- 1 | const { mock, mockResponse } = require('./mockResExport.js'); 2 | 3 | const mockController = {}; 4 | 5 | /* 6 | const dataContract = { 7 | 'Req@POST@/login': { username: 'string', age: 'number' }, 8 | 'Res@POST@/login': { success: 'boolean' }, 9 | 'Req@POST@/habits': { habitname: 'string', target: 'number' }, 10 | 'Res@POST@/habits': { currentHabits: 'array-boolean-7' }, 11 | }; 12 | */ 13 | 14 | mockController.generateMock = async (req, res, next) => { 15 | const { method, path } = req; 16 | const contractKey = `Req@${method}@${path}`; 17 | 18 | try { 19 | if (!res.locals.report.pass) { 20 | res.locals.mockRes = 'Type check Failed ❌'; 21 | return next(); 22 | } 23 | 24 | const mockRes = mockResponse(currentContract, contractKey); 25 | res.locals.mockRes = mockRes; 26 | return next(); 27 | } catch (error) { 28 | return next({ 29 | log: 'Express error in generateMock middleware', 30 | status: 400, 31 | message: { 32 | err: `mockController.generateMock: ERROR: ${error}`, 33 | }, 34 | }); 35 | } 36 | }; 37 | 38 | module.exports = mockController; 39 | -------------------------------------------------------------------------------- /src/express/testing_server/controllers/mockResExport.js: -------------------------------------------------------------------------------- 1 | const chance = require('chance').Chance(); 2 | 3 | const randomize = { 4 | // Primitive 5 | string: () => chance.string({ alpha: true }), // length, pool, alpha, casing, symbols 6 | letter: () => chance.letter(), // casing: lower 7 | number: () => chance.integer({ min: 0, max: 30 }), 8 | floating: () => chance.floating({ min: 0, max: 100 }), // min, max, fixed 9 | boolean: () => chance.bool(), 10 | falsy: () => chance.falsy(), 11 | 12 | // Text 13 | word: () => chance.word(), // syllables, length 14 | sentence: () => chance.sentence(), // words 15 | 16 | // Person 17 | fN: () => chance.first(), // gender 18 | lN: () => chance.last(), 19 | fullName: () => chance.name(), 20 | email: () => chance.email({ domain: 'email.com' }), 21 | address: () => chance.address(), 22 | city: () => chance.city(), 23 | country: () => chance.country({ full: true }), 24 | phone: () => chance.phone(), 25 | zip: () => chance.zip(), 26 | 27 | // Animal 28 | animal: () => chance.animal(), // type 29 | 30 | // Time 31 | date: () => chance.date({ string: true, year: 2022, month: 0 }), 32 | weekday: () => chance.weekday(), 33 | timestamp: () => chance.timestamp(), 34 | 35 | // Array Generator 36 | array: (content, length) => 37 | Array.from({ length: length }, randomize[content]), 38 | 39 | // Data Contract Method 40 | }; 41 | 42 | /** 43 | * Generate mock response based on data contract 44 | * @param contracts - data contracts 45 | * @param condition - key in data contracts 46 | */ 47 | 48 | function genMockResponse(contracts, condition) { 49 | // Check if condition exists in contracts 50 | if (!(condition in contracts)) return 'Condition not found!'; 51 | 52 | // Set the corresponding condition O(n), req@... => res@... 53 | let reqOrRes = condition[2] === 'q' ? 's' : 'q'; 54 | const resCondition = condition.replace(condition[2], reqOrRes); 55 | // Retreive the expected data contract from user 56 | const mockResponseTemplate = contracts[resCondition]; 57 | // Generate mock with homemade randomize obj method 58 | const mockRes = {}; 59 | for (let key in mockResponseTemplate) { 60 | const dataType = mockResponseTemplate[key]; 61 | if (dataType.includes('array')) { 62 | // Handle mock array here! 63 | const mockArray = handleArray(dataType); 64 | mockRes[key] = mockArray; 65 | } else { 66 | mockRes[key] = randomize[dataType](); 67 | } 68 | } 69 | return mockRes; 70 | } 71 | 72 | /** 73 | * Generate mock response based on data contract 74 | * @param dataTypeStr - extract contract key 'array-content-length' 75 | */ 76 | function handleArray(dataTypeStr) { 77 | // chancePrimitives handles random data type you want in your random generated array 78 | const chancePrimitives = [ 79 | // 'fN', 80 | // 'country', 81 | // 'word', 82 | // 'animal', 83 | // 'number', 84 | // 'boolean', 85 | // 'date', 86 | 'number', 87 | ]; 88 | const mockArray = []; 89 | const mockArrContent = dataTypeStr.split('-')[1]; 90 | const mockArrLength = dataTypeStr.split('-')[2]; 91 | const randomLength = randomize.number(); 92 | 93 | if (mockArrContent === 'any') { 94 | const length = mockArrLength === 'any' ? randomLength : mockArrLength; 95 | for (let i = 0; i < length; i += 1) { 96 | const randomNum = Math.floor(Math.random() * chancePrimitives.length); 97 | const dataType = chancePrimitives[randomNum]; 98 | const randomContent = randomize[dataType](); 99 | mockArray.push(randomContent); 100 | } 101 | return mockArray; 102 | } else if (mockArrLength === 'any') { 103 | return randomize.array(mockArrContent, randomLength); 104 | } 105 | } 106 | 107 | const dataContract = { 108 | 'Req@POST@/login': { username: 'string', age: 'number' }, 109 | 'Res@POST@/login': { success: 'boolean' }, 110 | 'Req@POST@/habits': { habitname: 'string', target: 'number' }, 111 | 'Res@POST@/habits': { currentHabits: 'array-any-any' }, 112 | }; 113 | 114 | 115 | exports.mock = randomize; 116 | exports.mockResponse = genMockResponse; 117 | -------------------------------------------------------------------------------- /src/express/testing_server/models/dbModel.js: -------------------------------------------------------------------------------- 1 | const { Pool } = require("pg"); 2 | // Link to ElephantSQL DB 3 | const PG_URI = process.env.DB_KEY; 4 | 5 | // Establish connection to DB 6 | const pool = new Pool({ 7 | connectionString: PG_URI, 8 | }); 9 | 10 | // Query to DB 11 | module.exports = { 12 | query: (text, params, callback) => { 13 | // console.log("Executed query", text); 14 | return pool.query(text, params, callback); 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /src/express/testing_server/testing_server.js: -------------------------------------------------------------------------------- 1 | const express2 = require("express"); 2 | const app2 = express2(); 3 | const server = require("http").createServer(app2); 4 | const WebSocket = require("ws"); 5 | 6 | const cors = require("cors"); 7 | 8 | app2.use(cors()); 9 | 10 | // console.log(process.cwd()); 11 | const dbController = require(path.resolve( 12 | __dirname, 13 | "../src/express/testing_server/controllers/dbController.js" 14 | )); 15 | 16 | const checkController = require(path.resolve( 17 | __dirname, 18 | "../src/express/testing_server/controllers/checkController.js" 19 | )); 20 | 21 | const mockController = require(path.resolve( 22 | __dirname, 23 | "../src/express/testing_server/controllers/mockController.js" 24 | )); 25 | 26 | app2.use(express.json()); 27 | app2.use(express.urlencoded({ extended: true })); 28 | 29 | const wss = new WebSocket.Server({ server: server }); 30 | 31 | let currentContract = {}; 32 | 33 | // receive current contract from contractual frontend 34 | app2.get("/contract/:token", dbController.getContent, (req, res) => { 35 | // console.log('updated contract:', currentContract); 36 | return res.status(200).json({ success: true }); 37 | }); 38 | 39 | wss.on("connection", (ws) => { 40 | // Log when new client connect to this server 41 | // console.log("********* New Client Connected"); 42 | ws.send("Welcome New Client"); 43 | // Trigger when server receives anything from a client 44 | ws.on("message", (message) => { 45 | //console.log(`received: %s`, message); 46 | ws.send(`2. SERVER 1234 GOT YOUR MESSAGE: ${message}`); 47 | }); 48 | 49 | app2.use( 50 | "/", 51 | (req, res, next) => { 52 | return next(); 53 | }, 54 | checkController.checkReq, 55 | mockController.generateMock, 56 | (req, res) => { 57 | //send a websocket message here 58 | ws.send(JSON.stringify(res.locals.report)); 59 | 60 | // Send back mock response 61 | res.status(200).send(res.locals.mockRes); 62 | } 63 | ); 64 | }); 65 | 66 | // app2.use('/', (req, res) => { 67 | // //send a websocket message here 68 | // console.log('HIT!!!!!!!!'); 69 | // ws.send('sent from middleware!!!!!!!!!!!!!!!!!!!!!!'); 70 | // res.status(200).send('AYOOOOOOOOOOOO!!!!!'); 71 | // // throw new Error(); 72 | // }); 73 | server.listen(1234, () => console.log(`LISTENING ON PORT 1234`)); 74 | 75 | // // Unknown route handler 76 | // app2.use((req, res) => res.status(404).send('You are in the wrong place! 😡')); 77 | 78 | // // Global error handler 79 | // app2.use((err, req, res, next) => { 80 | // const defaultErr = { 81 | // log: 'Express error handler caught unknown middleware error', 82 | // status: 400, 83 | // message: { err: 'An error occurred' }, 84 | // }; 85 | // const errorObj = Object.assign(defaultErr, err); 86 | // console.log(errorObj.log); 87 | // return res.status(errorObj.status).send(errorObj.message); 88 | // }); 89 | 90 | // app2.listen(PORT2, () => console.log(`Testing server listening on port ${PORT2}`)); 91 | 92 | module.exports = app2; 93 | -------------------------------------------------------------------------------- /src/express/user_info_server/controllers/commands.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE users( 2 | user_id SERIAL PRIMARY KEY, 3 | email VARCHAR(255) NOT NULL UNIQUE, 4 | name VARCHAR(255) NOT NULL, 5 | password VARCHAR(255) NOT NULL 6 | ) 7 | 8 | CREATE TABLE contracts( 9 | contract_id SERIAL PRIMARY KEY, 10 | title VARCHAR(255) NOT NULL, 11 | content VARCHAR(255) NOT NULL, 12 | token VARCHAR(255) NOT NULL, 13 | user_id INTEGER, 14 | FOREIGN KEY (user_id) REFERENCES users(user_id) 15 | ) 16 | 17 | CREATE TABLE users_contracts( 18 | user_id INTEGER, 19 | FOREIGN KEY (user_id) REFERENCES users(user_id), 20 | contract_id INTEGER, 21 | FOREIGN KEY (contract_id) REFERENCES contracts(contract_id), 22 | permission BOOLEAN 23 | ) 24 | 25 | 26 | -- Insert dummy data 27 | INSERT INTO users ( email, name, password) 28 | VALUES('yankun@gmail.com', 'Yankun', '1234') 29 | 30 | INSERT INTO contracts ( title, content, token, user_id) 31 | VALUES('Contractual', '{"Req@POST@/login": {"username": "string","age": "number"},"Res@POST@/login": {"success": "boolean"}}' , 'A1B2', 1) 32 | 33 | INSERT INTO contracts ( title, content, token, user_id) 34 | VALUES('Habitual', '{"Req@POST@/habits": {"habitname": "string","target": "number"},"Res@POST@/habits": {"currentHabits": "array-string-any"}}' , 'XY69', 1) 35 | 36 | INSERT INTO users_contracts (user_id, contract_id, permission) 37 | VALUES(1,1, true) 38 | INSERT INTO users_contracts (user_id, contract_id, permission) 39 | VALUES(1,2, true) 40 | 41 | 42 | SELECT c.* 43 | FROM users_contracts uc LEFT OUTER JOIN contracts c 44 | ON uc.user_id = c.user_id 45 | WHERE c.user_id = 1 46 | 47 | -- Delete row from table 48 | DELETE FROM contracts 49 | where contract_id = 2 50 | 51 | 52 | -- Re-serialize primary key 53 | ALTER SEQUENCE contracts_contract_id_seq RESTART WITH 1 54 | ALTER SEQUENCE users_user_id_seq RESTART WITH 1 55 | 56 | -- Delete table content with foreign key 57 | TRUNCATE TABLE users 58 | CASCADE; 59 | 60 | TRUNCATE TABLE contracts 61 | 62 | -- Add column to table 63 | ALTER TABLE contracts 64 | ADD COLUMN title VARCHAR(255) NOT NULL 65 | 66 | 67 | -- Alter Column to be Unique 68 | ALTER TABLE contracts ADD CONSTRAINT make_unique UNIQUE (title); 69 | 70 | -- Alter Column data type 71 | ALTER TABLE contracts 72 | ALTER COLUMN content TYPE VARCHAR -------------------------------------------------------------------------------- /src/express/user_info_server/controllers/dbController.js: -------------------------------------------------------------------------------- 1 | const db = require("../models/dbModel.js"); 2 | const bcrypt = require("bcrypt"); 3 | 4 | const dbController = {}; 5 | 6 | // Contract Route => Retrieve content based on token in contracts table 7 | dbController.getContent = async (req, res, next) => { 8 | // the user imports someone else's contract 9 | if (req.body.import) { 10 | const { name, token, userId } = req.body; 11 | 12 | const param = [name, token.toUpperCase()]; 13 | // get contract details for him 14 | try { 15 | const getContent = ` 16 | SELECT * FROM contracts 17 | WHERE token = $2 AND title = $1; 18 | `; 19 | 20 | const targetContent = await db.query(getContent, param); 21 | // targetContent returns a JSON object 22 | const parsedContent = JSON.parse(targetContent.rows[0].content); 23 | res.locals.content = { content: parsedContent }; 24 | } catch (error) { 25 | return next({ 26 | // log: 'Express error in getContent middleware', 27 | log: `dbController.getContent: ERROR: ${error}`, 28 | 29 | status: 400, 30 | message: { 31 | err: `dbController.getContent: ERROR: ${error}`, 32 | }, 33 | }); 34 | } 35 | 36 | // add this contract to history 37 | try { 38 | const getContentId = ` 39 | SELECT contract_id FROM contracts 40 | WHERE token = $1; 41 | `; 42 | 43 | const contractIdRes = await db.query(getContentId, [token.toUpperCase()]); 44 | // targetContent returns a JSON object 45 | const contractId = JSON.parse(contractIdRes.rows[0].contract_id); 46 | const param2 = [userId, contractId, false]; 47 | 48 | const addHistoryQuery = ` 49 | INSERT INTO users_contracts(user_id, contract_id, permission) 50 | VALUES($1, $2, $3) 51 | RETURNING * 52 | ;`; 53 | await db.query(addHistoryQuery, param2); 54 | return next(); 55 | } catch (error) { 56 | return next({ 57 | log: "Express error in adding to history in getContent middleware", 58 | status: 400, 59 | message: { 60 | err: `dbController.getContent: ERROR: ${error}`, 61 | }, 62 | }); 63 | } 64 | } 65 | 66 | // user selects his contract, and frontend needs detailed content of the contract 67 | else { 68 | const { token } = req.body; 69 | try { 70 | const getContent = ` 71 | SELECT * FROM contracts 72 | WHERE token = $1; 73 | `; 74 | 75 | const targetContent = await db.query(getContent, [token.toUpperCase()]); 76 | const parsedContent = JSON.parse(targetContent.rows[0].content); 77 | res.locals.content = { content: parsedContent }; 78 | return next(); 79 | } catch (error) { 80 | return next({ 81 | log: `dbController.getContent: ERROR: ${error}`, 82 | status: 400, 83 | message: { 84 | err: `dbController.getContent: ERROR: ${error}`, 85 | }, 86 | }); 87 | } 88 | } 89 | }; 90 | 91 | // Contract Route => Update content based on token in contracts table 92 | dbController.updateContent = async (req, res, next) => { 93 | const { content, token } = req.body; 94 | const param = [JSON.stringify(content), token.toUpperCase()]; 95 | try { 96 | const updateContent = ` 97 | UPDATE contracts SET content = $1 WHERE token = $2; 98 | `; 99 | await db.query(updateContent, param); 100 | return next(); 101 | } catch (error) { 102 | return next({ 103 | log: `dbController.updateContent: ERROR: ${error}`, 104 | // log: 'Express error in updateContent middleware', 105 | status: 400, 106 | message: { 107 | err: `dbController.updateContent: ERROR: ${error}`, 108 | }, 109 | }); 110 | } 111 | }; 112 | 113 | // Contract Route => Delete the entire contract 114 | // const deleteContractQuery = ` DELETE FROM contracts WHERE token = $1; ` 115 | 116 | // Contract Route => Create token and contract and store in contracts Table 117 | dbController.addContract = async (req, res, next) => { 118 | const { title, userId } = req.body; 119 | // function to generate random token 120 | function makeid(length) { 121 | let result = ""; 122 | const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 123 | const charactersLength = characters.length; 124 | for (let i = 0; i < length; i++) { 125 | result += characters.charAt(Math.floor(Math.random() * charactersLength)); 126 | } 127 | return result; 128 | } 129 | 130 | let token; 131 | // Check if token exists in DB 132 | while (true) { 133 | token = makeid(4); 134 | const checkTokenQuery = ` 135 | SELECT * FROM contracts 136 | WHERE token = $1; 137 | `; 138 | const checkToken = await db.query(checkTokenQuery, [token]); 139 | if (checkToken.rows.length == 0) break; 140 | } 141 | 142 | let contractId; 143 | const content = {}; 144 | // Store contract in contract table 145 | try { 146 | const param1 = [title, content, token, userId]; 147 | const addContractQuery = ` 148 | INSERT INTO contracts(title, content, token, user_id) 149 | VALUES($1, $2, $3, $4) 150 | RETURNING * 151 | ;`; 152 | const addContract = await db.query(addContractQuery, param1); 153 | // console.log(addContract); 154 | contractId = addContract["rows"][0]["contract_id"]; 155 | 156 | res.locals.token = token; 157 | } catch (error) { 158 | // res.locals.token = false; 159 | // return next(); 160 | return next({ 161 | log: `Express error in addContract middleware: ${error}`, 162 | status: 400, 163 | message: { 164 | err: `dbController.addContract: ERROR: ${error}`, 165 | }, 166 | }); 167 | } 168 | 169 | // Store contract in user-contract table 170 | try { 171 | const param2 = [userId, contractId, true]; 172 | const addHistoryQuery = ` 173 | INSERT INTO users_contracts(user_id, contract_id, permission) 174 | VALUES($1, $2, $3) 175 | RETURNING * 176 | ;`; 177 | await db.query(addHistoryQuery, param2); 178 | } catch (error) { 179 | return next({ 180 | log: `Express error in addContract middleware: ${error}`, 181 | status: 400, 182 | message: { 183 | err: `dbController.addContract: ERROR: ${error}`, 184 | }, 185 | }); 186 | } 187 | return next(); 188 | }; 189 | 190 | // Login Route => verify user info with users Table 191 | dbController.checkUser = async (req, res, next) => { 192 | const { email, password } = res.locals.loginUser; 193 | const param = [email]; 194 | try { 195 | const verifyQuery = ` 196 | SELECT * FROM users 197 | WHERE email = $1; 198 | `; 199 | const userInfo = await db.query(verifyQuery, param); 200 | // Verify if email already exists 201 | if (userInfo.rows[0] === undefined) { 202 | return res 203 | .status(404) 204 | .json({ success: false, message: "Incorrect Email!" }); 205 | } 206 | bcrypt.compare(password, userInfo.rows[0].password, (err, result) => { 207 | if (err) return err; 208 | // Result return false if plain pw doesn't match hashed pw 209 | if (!result) 210 | return res 211 | .status(404) 212 | .json({ success: false, message: "Incorrect Password!" }); 213 | const loginRes = { 214 | success: true, 215 | userId: userInfo.rows[0].user_id, 216 | userName: userInfo.rows[0].name, 217 | // tokens: 218 | // owns: 219 | }; 220 | res.locals.loginData = loginRes; 221 | return next(); 222 | }); 223 | } catch (error) { 224 | return next({ 225 | log: `Express error in checkUser middleware: ${error}`, 226 | status: 400, 227 | message: { 228 | err: `dbController.checkUser: ERROR: ${error}`, 229 | }, 230 | }); 231 | } 232 | }; 233 | 234 | dbController.getAccessList = async (req, res, next) => { 235 | const { userId } = res.locals.loginData; 236 | const param = [userId]; 237 | try { 238 | const tokenListQuery = ` 239 | SELECT c.title, c.token, uc.permission 240 | FROM users_contracts uc INNER JOIN contracts c 241 | ON uc.contract_id = c.contract_id 242 | WHERE uc.user_id = $1 243 | `; 244 | 245 | const accessList = await db.query(tokenListQuery, param); 246 | // Save accessible tokens and permission into res.locals.loginData 247 | res.locals.loginData.tokens = {}; 248 | res.locals.loginData.owns = []; 249 | accessList.rows.forEach((userAccess) => { 250 | const { title, token, permission } = userAccess; 251 | res.locals.loginData.tokens[title] = token; 252 | if (permission) res.locals.loginData.owns.push(token); 253 | }); 254 | // console.log(accessList.rows); 255 | // console.log(res.locals.loginData); 256 | return next(); 257 | } catch (error) { 258 | return next({ 259 | log: `Express error in getAccessList middleware: ${error}`, 260 | status: 400, 261 | message: { 262 | err: `dbController.getAccessList: ERROR: ${error}`, 263 | }, 264 | }); 265 | } 266 | }; 267 | 268 | // Sign up Route => save user info into users Table 269 | dbController.saveUser = async (req, res, next) => { 270 | const { name, email, password } = res.locals.newUser; 271 | const saltRounds = 10; 272 | 273 | // bcrypt magic, generates hashed password 274 | bcrypt.genSalt(saltRounds, async (err, salt) => { 275 | if (err) { 276 | throw err; 277 | } else { 278 | bcrypt.hash(password, saltRounds, async (err, hash) => { 279 | if (err) return err; 280 | try { 281 | const params = [name, email, hash]; 282 | const saveUserQuery = ` 283 | INSERT INTO users (name, email, password) 284 | VALUES($1, $2, $3) 285 | RETURNING * 286 | `; 287 | const newUser = await db.query(saveUserQuery, params); 288 | const userId = newUser.rows[0].user_id; 289 | res.locals.userInfo = { 290 | success: true, 291 | userId: userId, 292 | userName: name, 293 | tokens: {}, 294 | owns: [], 295 | }; 296 | return next(); 297 | } catch (error) { 298 | return next({ 299 | log: `Express error in saveUser middleware: ${error}`, 300 | status: 400, 301 | message: { 302 | err: `dbController.saveUser: ERROR: ${error}`, 303 | }, 304 | }); 305 | } 306 | }); 307 | } 308 | }); 309 | }; 310 | 311 | module.exports = dbController; 312 | -------------------------------------------------------------------------------- /src/express/user_info_server/controllers/userController.js: -------------------------------------------------------------------------------- 1 | const userController = {}; 2 | 3 | // Sign-up extract and save new user's account info from frontend into res.locals 4 | userController.addUser = (req, res, next) => { 5 | const userProps = ['name', 'email', 'password']; 6 | res.locals.newUser = {}; 7 | 8 | for (const prop of userProps) { 9 | if (!req.body[prop]) { 10 | return next({ 11 | log: 'UserController.addUser ERROR: Properties on request body undefined', 12 | message: { 13 | err: 'UserController.addUser ERROR: Incorrect data received', 14 | }, 15 | }); 16 | } 17 | res.locals.newUser[prop] = req.body[prop]; 18 | } 19 | return next(); 20 | }; 21 | 22 | // Extract user email/pw from frontent, and store into res.locals 23 | userController.checkUser = (req, res, next) => { 24 | const userProps = ['email', 'password']; 25 | res.locals.loginUser = {}; 26 | for (const prop of userProps) { 27 | if (!req.body[prop]) { 28 | return next({ 29 | log: 'UserController.checkUser ERROR: Properties on request body undefined', 30 | message: { 31 | err: 'UserController.checkUser ERROR: Incorrect data received', 32 | }, 33 | }); 34 | } 35 | res.locals.loginUser[prop] = req.body[prop]; 36 | } 37 | return next(); 38 | }; 39 | 40 | module.exports = userController; 41 | -------------------------------------------------------------------------------- /src/express/user_info_server/models/dbModel.js: -------------------------------------------------------------------------------- 1 | const { Pool } = require('pg'); 2 | 3 | // Link to ElephantSQL DB 4 | const PG_URI = process.env.DB_KEY; 5 | // const PG_URI = 6 | // 'postgres://zzzenrbw:qKcrtsvOIBu_eBn7vP9l83Nk0V_ij_md@heffalump.db.elephantsql.com/zzzenrbw'; 7 | // Establish connection to DB 8 | const pool = new Pool({ 9 | connectionString: PG_URI, 10 | }); 11 | 12 | // Query to DB 13 | module.exports = { 14 | query: (text, params, callback) => { 15 | // console.log('Executed query', text); 16 | return pool.query(text, params, callback); 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /src/express/user_info_server/routes/contract.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const dbController = require('../controllers/dbController'); 3 | const cors = require('cors'); 4 | 5 | const router = express.Router(); 6 | 7 | // // Get content with token Route 8 | router.post('/details', dbController.getContent, (req, res) => { 9 | return res.status(200).json(res.locals.content); 10 | }); 11 | 12 | router.patch('/', dbController.updateContent, (req, res) => { 13 | return res.status(200).json("success"); 14 | }); 15 | 16 | // Create Contract Route 17 | router.post('/add', dbController.addContract, (req, res) => { 18 | return res.status(200).json(res.locals.token); 19 | }); 20 | 21 | module.exports = router; 22 | -------------------------------------------------------------------------------- /src/express/user_info_server/routes/login.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const dbController = require('../controllers/dbController'); 3 | const userController = require('../controllers/userController'); 4 | 5 | const router = express.Router(); 6 | 7 | // user login ROUTE HANDLER 8 | router.post( 9 | '/', 10 | userController.checkUser, 11 | dbController.checkUser, 12 | dbController.getAccessList, 13 | (req, res) => { 14 | // frontend receives and store userId in redux 15 | return res.status(200).json(res.locals.loginData); 16 | } 17 | ); 18 | module.exports = router; 19 | -------------------------------------------------------------------------------- /src/express/user_info_server/routes/signup.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const dbController = require('../controllers/dbController'); 3 | const userController = require('../controllers/userController'); 4 | 5 | const router = express.Router(); 6 | 7 | // adding a new user ROUTE HANDLER 8 | router.post('/', userController.addUser, dbController.saveUser, (req, res) => { 9 | //return res.status(200).json(res.locals.result); 10 | return res.status(200).json(res.locals.userInfo); 11 | }); 12 | 13 | module.exports = router; 14 | -------------------------------------------------------------------------------- /src/express/user_info_server/user_server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | const path = require('path'); 4 | require('dotenv').config(); 5 | // Routes Import 6 | const contractRouter = require(path.resolve( 7 | __dirname, 8 | '../src/express/user_info_server/routes/contract.js' 9 | )); 10 | 11 | const signupRouter = require(path.resolve( 12 | __dirname, 13 | '../src/express/user_info_server/routes/signup.js' 14 | )); 15 | 16 | const loginRouter = require(path.resolve( 17 | __dirname, 18 | '../src/express/user_info_server/routes/login.js' 19 | )); 20 | 21 | const PORT = 4321; 22 | 23 | app.use(express.json()); 24 | app.use(express.urlencoded({ extended: true })); 25 | 26 | // Define Routes Handler 27 | 28 | app.get('/', function (req, res) { 29 | res.send('User Server is ready!'); 30 | }); 31 | 32 | // console.log('-------Process.env: ', process.env.DB_KEY); 33 | 34 | app.use('/contract', contractRouter); 35 | app.use('/register', signupRouter); 36 | app.use('/login', loginRouter); 37 | 38 | // Unknown route handler 39 | app.use((req, res) => res.status(404).send('You are in the wrong place! 😡')); 40 | 41 | // Global error handler 42 | app.use((err, req, res, next) => { 43 | const defaultErr = { 44 | log: 'Express error handler caught unknown middleware error', 45 | status: 400, 46 | message: { err: 'An error occurred' }, 47 | }; 48 | const errorObj = Object.assign(defaultErr, err); 49 | console.log(errorObj.log); 50 | return res.status(errorObj.status).send(errorObj.message); 51 | }); 52 | 53 | app.listen(PORT, () => console.log(`User server listening on port ${PORT}`)); 54 | 55 | module.exports = app; 56 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Contractual 10 | 11 | 12 |
    13 | 14 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow } = require('electron'); 2 | const path = require('path'); 3 | process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'; 4 | 5 | function createSplashWindow() { 6 | const win = new BrowserWindow({ 7 | width: 500, 8 | height: 300, 9 | // backgroundColor: '#6e707e', 10 | frame: false, 11 | transparent: true, 12 | webPreferences: { 13 | //preload: path.join(__dirname, "./preload.js"), 14 | nodeIntegration: true, 15 | // worldSafeExecuteJavascript: true, 16 | contextIsolation: false, 17 | // devTools: false, 18 | }, 19 | }); 20 | 21 | win.loadFile(`${__dirname}/splash.html`); 22 | 23 | return win; 24 | } 25 | 26 | function createWindow() { 27 | const win = new BrowserWindow({ 28 | width: 1200, 29 | height: 800, 30 | minWidth: 600, 31 | backgroundColor: '#111827', 32 | // No overlapping with splash page 33 | show: false, 34 | webPreferences: { 35 | //preload: path.join(__dirname, "./preload.js"), 36 | nodeIntegration: true, 37 | // worldSafeExecuteJavascript: true, 38 | contextIsolation: false, 39 | // Access to chrome dev tool 40 | devTools: true, 41 | }, 42 | }); 43 | 44 | win.loadFile(`${__dirname}/index.html`); 45 | 46 | // Open the DevTools. 47 | win.webContents.openDevTools(); 48 | 49 | return win; 50 | } 51 | 52 | app.whenReady().then(() => { 53 | const splash = createSplashWindow(); 54 | const mainApp = createWindow(); 55 | 56 | mainApp.once('ready-to-show', () => { 57 | setTimeout(() => { 58 | splash.destroy(); 59 | mainApp.show(); 60 | }, 2000); 61 | }); 62 | 63 | // app.on('activate', () => { 64 | // if (BrowserWindow.getAllWindows().length === 0) createWindow(); 65 | // }); 66 | }); 67 | 68 | app.on('window-all-closed', () => { 69 | if (process.platform !== 'darwin') app.quit(); 70 | }); 71 | -------------------------------------------------------------------------------- /src/preload.ts: -------------------------------------------------------------------------------- 1 | // window.addEventListener('DOMContentLoaded', () => { 2 | // const replaceText = (selector, text) => { 3 | // const element = document.getElementById(selector) 4 | // if (element) element.innerText = text 5 | // } 6 | 7 | // for (const dependency of ['chrome', 'node', 'electron']) { 8 | // replaceText(`${dependency}-version`, process.versions[dependency]) 9 | // } 10 | // }) -------------------------------------------------------------------------------- /src/renderer.ts: -------------------------------------------------------------------------------- 1 | // // let server = require('../src/server'); 2 | 3 | // let express = require('express'); 4 | // let test = express(); 5 | // test.get('/', function(req, res) { 6 | // res.send("Hello world! Contractual is here!"); 7 | // }); 8 | // let server = test.listen(3000, function () { 9 | // console.log('Express server listening on port ' + server.address().port); 10 | // }); -------------------------------------------------------------------------------- /src/splash.css: -------------------------------------------------------------------------------- 1 | .contractual-logo { 2 | width: 55px; 3 | height: 55px; 4 | /* justify-content: center; */ 5 | /* animation: contractual-logo-spin infinite 15s linear; */ 6 | /* opacity: 50%; */ 7 | opacity: 1; 8 | animation: logo 3s ease-in 1; 9 | } 10 | 11 | @keyframes logo { 12 | 0% { 13 | opacity: 0%; 14 | } 15 | 60% { 16 | opacity: 80%; 17 | } 18 | 100% { 19 | opacity: 100%; 20 | } 21 | } 22 | 23 | .splash { 24 | /* width: 400; 25 | height: 200; */ 26 | background-color: black; 27 | margin: 0; 28 | padding: 0; 29 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, 30 | Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 31 | /* color: white; */ 32 | } 33 | 34 | .center { 35 | display: flex; 36 | flex-direction: column; 37 | text-align: center; 38 | justify-content: center; 39 | align-items: center; 40 | min-height: 100vh; 41 | } 42 | 43 | .ring { 44 | position: absolute; 45 | width: 160px; 46 | height: 160px; 47 | border-radius: 50%; 48 | /* background: pink; */ 49 | animation: ring 3s linear infinite; 50 | } 51 | 52 | @keyframes ring { 53 | 0% { 54 | transform: rotate(0deg); 55 | box-shadow: 1px 5px 2px #020c65; 56 | } 57 | 50% { 58 | transform: rotate(180deg); 59 | box-shadow: 1px 5px 2px #0f76cf; 60 | } 61 | 100% { 62 | transform: rotate(359deg); 63 | box-shadow: 1px 5px 2px #020c65; 64 | } 65 | } 66 | 67 | .ring:before { 68 | position: absolute; 69 | content: ''; 70 | left: 0; 71 | top: 0; 72 | height: 100%; 73 | width: 100%; 74 | border-radius: 50%; 75 | box-shadow: 0 0 5px rgba(255, 255, 255, 0.3); 76 | } 77 | 78 | .center span { 79 | color: #000000; 80 | font-size: 14px; 81 | text-transform: uppercase; 82 | letter-spacing: 1px; 83 | line-height: 35px; 84 | animation: text 3s ease-in-out infinite; 85 | } 86 | 87 | .center footer { 88 | color: rgb(136, 134, 134); 89 | font-size: 10px; 90 | position: absolute; 91 | bottom: 3%; 92 | right: 3%; 93 | } 94 | 95 | @keyframes text { 96 | 60% { 97 | color: rgb(100, 99, 99); 98 | } 99 | /* 66% { 100 | color: black; 101 | } */ 102 | } 103 | -------------------------------------------------------------------------------- /src/splash.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Contractual 8 | 9 | 10 | 11 |
    12 |
    13 | 14 | Loading... 15 |
    V1.0.0 Beta
    16 |
    17 | 18 | 19 | -------------------------------------------------------------------------------- /src/state/features/backLogSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from '@reduxjs/toolkit'; 2 | 3 | type LogSuccessItem = { 4 | endpoint: string, 5 | error: string[], 6 | method: string, 7 | pass: boolean, 8 | time: string 9 | } 10 | 11 | type LogErrorItem = { 12 | endpoint: string, 13 | error: string[], 14 | method: string, 15 | pass: boolean, 16 | time: string 17 | } 18 | 19 | type LogItem = (LogSuccessItem | LogErrorItem) 20 | type InitialState = LogItem[]; 21 | 22 | const initialState: InitialState = []; 23 | 24 | export const backLogSlice = createSlice({ 25 | name: "backLog", 26 | initialState, 27 | reducers: { 28 | updateLog: (state, action: PayloadAction) => { 29 | state.unshift(action.payload); 30 | }, 31 | clearLog: (state) => { 32 | state = []; 33 | } 34 | } 35 | }) 36 | 37 | export const { updateLog, clearLog } = backLogSlice.actions; 38 | 39 | export default backLogSlice.reducer; -------------------------------------------------------------------------------- /src/state/features/contractSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit"; 2 | import axios from "axios"; 3 | import { string } from "yup"; 4 | 5 | ///// THUNK FUNCTIONS 6 | 7 | 8 | ////// SHOULD MOVE TYPES TO ANOTHER FILE 9 | type CurrentContract = { 10 | [key: string]: Contracts 11 | } 12 | type Contracts = { 13 | [key: string]: string 14 | } 15 | 16 | type LoadContract = { 17 | token: string, 18 | contract: CurrentContract 19 | } 20 | type AddContract = { 21 | name: string, 22 | token: string 23 | } 24 | 25 | type JoinContract = { 26 | name: string, 27 | token: string, 28 | contract: CurrentContract 29 | } 30 | type ContractState = { 31 | userName: string, 32 | userId: number, 33 | tokens: Contracts, 34 | owns: string[], 35 | currentContractToken: string, 36 | currentContract: CurrentContract, 37 | // frontEndPort: string 38 | // backEndPort: string 39 | // status: string 40 | }; 41 | 42 | 43 | /////// SLICE 44 | const initialState: ContractState = { 45 | userName: '', 46 | userId: 0, 47 | tokens: {}, 48 | owns: [], 49 | currentContractToken: '', 50 | currentContract: {}, 51 | // status: '', 52 | }; 53 | 54 | export const contractSlice = createSlice({ 55 | name: "contract", 56 | initialState, 57 | reducers: { 58 | getContract: (state, action: PayloadAction) => { 59 | state.currentContractToken = action.payload; 60 | // state.contract = response data? 61 | // this may need to be in extra reducers after building asyncThunk function 62 | }, 63 | updateContract: (state, action: PayloadAction) => { 64 | state.currentContract = action.payload 65 | }, 66 | // invoke on successful login 67 | getUserData: (state, action) => { 68 | state.userName = action.payload.userName; 69 | state.userId = action.payload.userId; 70 | state.tokens = action.payload.tokens; 71 | state.owns = action.payload.owns; 72 | }, 73 | loadContract: (state, action: PayloadAction) => { 74 | state.currentContract = action.payload.contract; 75 | state.currentContractToken = action.payload.token; 76 | }, 77 | addContract: (state, action: PayloadAction) => { 78 | state.currentContract = initialState.currentContract; 79 | state.currentContractToken = action.payload.token; 80 | state.owns.push(action.payload.token); 81 | state.tokens[action.payload.name] = action.payload.token; 82 | }, 83 | joinContract: (state, action: PayloadAction) => { 84 | state.currentContract = action.payload.contract; 85 | state.currentContractToken = action.payload.token; 86 | state.tokens[action.payload.name] = action.payload.token; 87 | }, 88 | // changeCurrentContractToken: () => {}, 89 | // createNewContractToken: () => {}, 90 | // changeFrontEndPort: (state, action: PayloadAction) => { 91 | // state.frontEndPort = action.payload 92 | // }, 93 | // changeBackEndPort: (state, action: PayloadAction) => { 94 | // state.backEndPort = action.payload 95 | // }, 96 | // deleteFromContract:, 97 | }, 98 | // extraReducers(builder) { 99 | // builder 100 | // .addCase(getUserData.pending, (state, action) => { 101 | // state.status = 'loading' 102 | // }) 103 | // .addCase(getUserData.fulfilled, (state, action) => { 104 | // state.status = 'success' 105 | // //add functionality here 106 | // state.userName = action.payload.userName, 107 | // state.userId = action.payload.userId, 108 | // state.contracts = action.payload.contracts, 109 | // state.owner = action.payload.owner 110 | // }) 111 | // .addCase(getUserData.rejected, (state, action) => { 112 | // state.status = 'failed' 113 | // }) 114 | // } 115 | // this is where you can respond to actions from other slices or asyncThunk functions 116 | // use builder syntax 117 | }); 118 | 119 | export const { getContract, updateContract, getUserData, loadContract, addContract, joinContract } = contractSlice.actions; 120 | 121 | export default contractSlice.reducer; -------------------------------------------------------------------------------- /src/state/features/counterSlice.ts: -------------------------------------------------------------------------------- 1 | //RTK follows the ducks pattern and combines reducers, actions, and constants in one file called a slice. Each slice will provide an initial state and a reducer function for an object in store. 2 | 3 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 4 | 5 | // define types of contents in your state object with interface or object type 6 | type CounterState = { 7 | counter: number; 8 | } 9 | 10 | const initialState: CounterState = { 11 | counter: 0 12 | } 13 | 14 | // create slice builds reducers, actions, and action creators under the hood 15 | export const counterSlice = createSlice({ 16 | name: "counter", 17 | initialState, 18 | // add reducer methods here 19 | reducers: { 20 | // state provided by redux automatically 21 | // PayloadAction is built in type, specify with < > 22 | increment: (state, action: PayloadAction) => { 23 | state.counter = action.payload + 1; 24 | }, 25 | decrement: (state, action: PayloadAction) => { 26 | state.counter = action.payload - 1; 27 | }, 28 | } 29 | }); 30 | 31 | // console.log(counterSlice) // to see what it provides 32 | 33 | // export your actions here by destructuring counterSlice 34 | export const { increment, decrement } = counterSlice.actions; 35 | 36 | // export the reducer built by counterSlice // contains reducers methods 37 | export default counterSlice.reducer; -------------------------------------------------------------------------------- /src/state/features/frontLogSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from '@reduxjs/toolkit'; 2 | 3 | type LogItem = { 4 | endpoint: string, 5 | error: string[], 6 | method: string, 7 | pass: boolean, 8 | time: string 9 | } 10 | 11 | type InitialState = LogItem[]; 12 | 13 | const initialState: InitialState = []; 14 | 15 | export const frontLogSlice = createSlice({ 16 | name: "frontLog", 17 | initialState, 18 | reducers: { 19 | updateLog: (state, action: PayloadAction) => { 20 | state.unshift(action.payload); 21 | }, 22 | clearLog: (state) => { 23 | state = []; 24 | } 25 | } 26 | }) 27 | 28 | export const { updateLog, clearLog } = frontLogSlice.actions; 29 | 30 | export default frontLogSlice.reducer; -------------------------------------------------------------------------------- /src/state/features/modalsSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from '@reduxjs/toolkit'; 2 | 3 | type ModalsState = { 4 | modalNotification: boolean; 5 | } 6 | 7 | const initialState: ModalsState = { 8 | modalNotification: false 9 | } 10 | 11 | export const modalsSlice = createSlice({ 12 | name: 'newContract', 13 | initialState, 14 | reducers: { 15 | showNotification: (state, action: PayloadAction) => { 16 | state.modalNotification = action.payload; 17 | }, 18 | } 19 | }); 20 | 21 | export const { showNotification } = modalsSlice.actions; 22 | 23 | export default modalsSlice.reducer; 24 | 25 | /** NOT BEING USED */ -------------------------------------------------------------------------------- /src/state/features/userSlice.ts: -------------------------------------------------------------------------------- 1 | // import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit"; 2 | 3 | 4 | 5 | // type Contracts = { 6 | // [key: string]: string 7 | // } 8 | 9 | // type UserState = { 10 | // userName: string, 11 | // userID: number, 12 | // contracts: Contracts, 13 | // owner: string[] 14 | // } 15 | 16 | // const initialState: UserState = { 17 | // userName: '', 18 | // userID: 0, 19 | // contracts: {}, 20 | // owner: [] 21 | // } 22 | 23 | // export const userSlice = createSlice({ 24 | // name: "user", 25 | // initialState, 26 | // reducers: { 27 | // } 28 | // }) 29 | -------------------------------------------------------------------------------- /src/state/store.ts: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit'; 2 | // importing the default export from counterSlice, this is a reducer 3 | import counterReducer from './features/counterSlice'; 4 | import contractReducer from './features/contractSlice'; 5 | import modalsReducer from './features/modalsSlice'; 6 | import frontLogReducer from './features/frontLogSlice'; 7 | import backLogReducer from './features/backLogSlice'; 8 | 9 | export const store = configureStore({ 10 | reducer: { 11 | // add reducers from each slice 12 | contract: contractReducer, 13 | counter: counterReducer, 14 | modals: modalsReducer, 15 | frontLog: frontLogReducer, 16 | backLog: backLogReducer, 17 | } 18 | }) 19 | 20 | // state type for typescript 21 | export type RootState = ReturnType; 22 | 23 | export type AppDispatch = typeof store.dispatch; -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | theme: {}, 3 | variants: {}, 4 | plugins: [ 5 | require('@tailwindcss/forms'), 6 | ], 7 | content: [ 8 | './src/**/*.{tsx, html}' 9 | ], 10 | }; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "CommonJS", 4 | "jsx": "react-jsx", 5 | "esModuleInterop": true, 6 | } 7 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | // const Dotenv = require('dotenv-webpack'); 4 | 5 | module.exports = [ 6 | { 7 | mode: 'development', 8 | entry: './src/main.ts', 9 | target: 'electron-main', 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.ts$/, 14 | include: /src/, 15 | use: [{ loader: 'ts-loader' }], 16 | }, 17 | ], 18 | }, 19 | output: { 20 | path: __dirname + '/dist', 21 | filename: 'main.js', 22 | }, 23 | }, 24 | // { 25 | // mode: 'development', 26 | // entry: './src/preload.ts', 27 | // target: 'electron-preload', 28 | // module: { 29 | // rules: [{ 30 | // test: /\.ts$/, 31 | // include: /src/, 32 | // use: [{ loader: 'ts-loader' }] 33 | // }] 34 | // }, 35 | // output: { 36 | // path: __dirname + '/dist', 37 | // filename: 'preload.js' 38 | // } 39 | // }, 40 | { 41 | mode: 'development', 42 | entry: './src/React.tsx', 43 | target: 'electron-renderer', 44 | devtool: 'source-map', 45 | module: { 46 | rules: [ 47 | { 48 | test: /\.ts(x?)$/, 49 | include: /src/, 50 | use: [{ loader: 'ts-loader' }], 51 | }, 52 | { 53 | test: /\.s?css$/, 54 | use: ['style-loader', 'css-loader', 'sass-loader', 'postcss-loader'], 55 | exclude: /\.module\.s?(c|a)ss$/, 56 | }, 57 | ], 58 | }, 59 | output: { 60 | path: __dirname + '/dist', 61 | filename: 'react.js', 62 | }, 63 | plugins: [ 64 | // new Dotenv(), 65 | new HtmlWebpackPlugin({ 66 | filename: 'index.html', 67 | template: './src/index.html', 68 | chunks: ['main'], 69 | }), 70 | // new HtmlWebpackPlugin({ 71 | // filename: 'splash.html', 72 | // template: './src/splash.html', 73 | // chunks: ['main'], 74 | // }), 75 | ], 76 | resolve: { 77 | extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'], 78 | }, 79 | }, 80 | // { 81 | // mode: 'development', 82 | // entry: './src/renderer.ts', 83 | // target: 'electron-renderer', 84 | // module: { 85 | // rules: [{ 86 | // test: /\.ts$/, 87 | // include: /src/, 88 | // use: [{ loader: 'ts-loader' }] 89 | // }] 90 | // }, 91 | // output: { 92 | // path: __dirname + '/dist', 93 | // filename: 'renderer.js' 94 | // } 95 | // }, 96 | ]; 97 | --------------------------------------------------------------------------------