├── .eslintrc ├── .gitignore ├── License ├── README.api.md ├── README.app.md ├── README.dev ├── README.md ├── api ├── code │ ├── config.js │ └── index.js ├── config │ ├── index.js │ └── makeConfig.js ├── controller │ ├── create.js │ ├── delete.js │ ├── get.js │ ├── getOne.js │ ├── index.js │ ├── indexFile.js │ ├── makeAPI.js │ └── update.js ├── graphql │ ├── createIndex.js │ ├── createResolvers.js │ ├── index.js │ ├── makeGraphql.js │ └── tcString.js ├── makeApi.js ├── mongodb │ ├── connectionIndex.js │ ├── connectionMongo.js │ ├── index.js │ ├── makeConnection.js │ ├── makeModelIndex.js │ ├── makeSchema.js │ └── modelIndex.js ├── redis │ ├── connectionRedis.js │ └── index.js ├── router │ ├── index.js │ ├── indexCode.js │ ├── makeRouter.js │ └── routerCode.js ├── server │ ├── dockerFile.js │ ├── eslint.json │ ├── index.js │ ├── package.json │ ├── readme.js │ ├── serverIndex.js │ ├── userCan.js │ ├── writeDockerIgnore.js │ ├── writeEslint.js │ ├── writeGitIgnore.js │ └── writePackageJSON.js ├── swagger │ ├── index.js │ ├── makeProperties.js │ ├── makeSwaggerModelDefinitions.js │ ├── safeDefault.js │ ├── safeType.js │ └── schemaDefinition.js ├── tests │ ├── fakeObject.js │ ├── genData.js │ ├── graphqlSafe.js │ ├── index.js │ ├── makeGraphqlTests.js │ ├── makeRestTests.js │ ├── makeTests.js │ ├── randomPropertyChange.js │ └── subDocHelper.js └── utils │ ├── createUpdateCode.js │ ├── createValidationCode.js │ ├── extraParams.js │ ├── getSchemaQueryDefinitions.js │ ├── getSearchCode.js │ ├── getValidationCode.js │ ├── index.js │ ├── returnValueBasedOnType.js │ ├── sugarGenerated.js │ ├── uppercase.js │ └── validateSchema.js ├── app ├── components │ ├── createChipComponent.js │ ├── createChipInputComponent.js │ ├── createForm.js │ ├── createSnackbar.js │ ├── createTable.js │ ├── getReactState.js │ ├── getTableColumns.js │ ├── makeComponents.js │ ├── tagsComponent.js │ ├── textFieldByType.js │ ├── textFieldsByType.js │ └── typeToValue.js ├── makeApp.js ├── pages │ ├── _app.js │ ├── _document.js │ ├── about.js │ ├── index.js │ └── makePages.js ├── src │ ├── Link.js │ ├── ProTip.js │ ├── apiCall.js │ ├── makeSrc.js │ └── theme.js └── statics │ ├── babelrc.js │ ├── config.js │ ├── dockerfile.js │ ├── gitignore.js │ ├── makeStatics.js │ ├── next-config.js │ ├── package.js │ └── readme.js ├── embeddable ├── createHTMLCreateForm.js ├── createHTMLTable.js ├── createJSCreateForm.js ├── createJSTable.js └── makeEmbeddable.js ├── graphql-mutation.png ├── graphql-schema.png ├── graphql.png ├── index.js ├── logo.png ├── monkey.png ├── package-lock.json ├── package.json ├── sample-schemas ├── app.json ├── auth.json ├── deployment.json ├── device.json ├── monkey.json ├── multi.json ├── secret.json ├── simple.json ├── user-can.json └── user.json ├── start.png ├── table-component.png └── user-form.png /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": false, 4 | "es6": true, 5 | "mocha": true, 6 | "node": true, 7 | "worker": false, 8 | "jest": true 9 | }, 10 | "extends": ["airbnb"], 11 | "parserOptions": { 12 | "ecmaVersion": 2018, 13 | "sourceType": "module", 14 | "ecmaFeatures": { 15 | "globalReturn": true, 16 | "impliedStrict": true, 17 | "destructuring": true, 18 | "restParams": true, 19 | "spread": true 20 | } 21 | }, 22 | "rules": { 23 | "array-bracket-spacing": "warn", 24 | "array-callback-return": "warn", 25 | "arrow-body-style": "off", 26 | "arrow-parens": "off", 27 | "arrow-spacing": [ 28 | "warn", 29 | { 30 | "after": true, 31 | "before": true 32 | } 33 | ], 34 | "camelcase": "off", 35 | "class-methods-use-this": "warn", 36 | "comma-dangle": [ 37 | "warn", 38 | { 39 | "arrays": "only-multiline", 40 | "objects": "only-multiline", 41 | "imports": "only-multiline", 42 | "exports": "only-multiline", 43 | "functions": "ignore" 44 | } 45 | ], 46 | "consistent-return": "off", 47 | "function-paren-newline": "off", 48 | "global-require": "off", 49 | "guard-for-in": "off", 50 | "implicit-arrow-linebreak": "off", 51 | "import/extensions": "warn", 52 | "import/first": "off", 53 | "import/newline-after-import": "off", 54 | "import/no-extraneous-dependencies": "off", 55 | "import/no-named-as-default": "warn", 56 | "import/no-named-as-default-member": "warn", 57 | "import/no-useless-path-segments": "warn", 58 | "import/order": "off", 59 | "import/prefer-default-export": "off", 60 | "indent": "warn", 61 | "jsx-a11y/alt-text": "off", 62 | "jsx-a11y/anchor-is-valid": "off", 63 | "jsx-a11y/click-events-have-key-events": "off", 64 | "jsx-a11y/label-has-associated-control": "off", 65 | "jsx-a11y/label-has-for": "off", 66 | "jsx-a11y/media-has-caption": "off", 67 | "jsx-a11y/mouse-events-have-key-events": "off", 68 | "jsx-a11y/no-noninteractive-element-interactions": "off", 69 | "jsx-a11y/no-noninteractive-tabindex": "off", 70 | "jsx-a11y/no-static-element-interactions": "off", 71 | "lines-between-class-members": "off", 72 | "max-len": [ 73 | "off", 74 | { 75 | "code": 98, 76 | "comments": 120, 77 | "ignoreRegExpLiterals": true, 78 | "ignoreStrings": true, 79 | "ignoreTemplateLiterals": true, 80 | "ignoreUrls": true, 81 | "tabWidth": 2 82 | } 83 | ], 84 | "new-parens": "warn", 85 | "no-bitwise": "off", 86 | "no-case-declarations": "warn", 87 | "no-console": [ 88 | "warn", 89 | { 90 | "allow": ["error"] 91 | } 92 | ], 93 | "no-continue": "off", 94 | "no-else-return": "off", 95 | "no-empty": "warn", 96 | "no-extra-boolean-cast": "off", 97 | "no-lonely-if": "warn", 98 | "no-mixed-operators": "off", 99 | "no-mixed-spaces-and-tabs": "warn", 100 | "no-multi-assign": "off", 101 | "no-multiple-empty-lines": "warn", 102 | "no-multi-spaces": "warn", 103 | "no-param-reassign": "off", 104 | "no-plusplus": "off", 105 | "no-prototype-builtins": "off", 106 | "no-restricted-globals": "off", 107 | "no-restricted-properties": "off", 108 | "no-restricted-syntax": "warn", 109 | "no-return-assign": "off", 110 | "no-return-await": "warn", 111 | "no-shadow": "warn", 112 | "no-tabs": "warn", 113 | "no-trailing-spaces": "warn", 114 | "no-undef-init": "off", 115 | "no-underscore-dangle": "off", 116 | "no-unneeded-ternary": "off", 117 | "no-unreachable": "warn", 118 | "no-unused-vars": ["warn", { 119 | "ignoreRestSiblings": true 120 | }], 121 | "no-useless-escape": "warn", 122 | "no-useless-return": "warn", 123 | "no-warning-comments": "off", 124 | "object-curly-newline": [ 125 | "off", 126 | { 127 | "ObjectExpression": { "multiline": true, "minProperties": 3 }, 128 | "ObjectPattern": { "multiline": true, "minProperties": 3 }, 129 | "ImportDeclaration": { "multiline": true, "minProperties": 3 }, 130 | "ExportDeclaration": { "multiline": true, "minProperties": 3 } 131 | } 132 | ], 133 | "object-curly-spacing": "off", 134 | "object-property-newline": "off", 135 | "operator-assignment": "off", 136 | "operator-linebreak": "off", 137 | "padded-blocks": "off", 138 | "prefer-const": "warn", 139 | "prefer-destructuring": "off", 140 | "prefer-promise-reject-errors": "warn", 141 | "prefer-spread": "warn", 142 | "prefer-template": "warn", 143 | "quotes": "off", 144 | "semi": "warn", 145 | "space-before-function-paren": "off", 146 | "spaced-comment": "off", 147 | "space-in-parens": "warn" 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 SugarKubes, Wrannaman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.api.md: -------------------------------------------------------------------------------- 1 | # Instantly Generate Rest & GraphQL APIs 🔥 2 | 3 | [![Follow on Twitter](https://img.shields.io/twitter/follow/andrewpierno.svg?label=follow)](https://twitter.com/andrewpierno) 4 | [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/sugarkubes/generators.svg)](http://isitmaintained.com/project/sugarkubes/generators "Average time to resolve an issue") 5 | [![Percentage of issues still open](http://isitmaintained.com/badge/open/sugarkubes/generators.svg)](http://isitmaintained.com/project/sugarkubes/generators "Percentage of issues still open") 6 | [![npm package](https://img.shields.io/npm/v/sugar-generate/latest.svg)](https://www.npmjs.com/package/sugar-generate) 7 | [![NPM Downloads](https://img.shields.io/npm/dt/sugar-generate.svg?style=flat)](https://npmcharts.com/compare/sugar-generate?minimal=true) 8 | 9 | 10 | ![Sugar Generator - API Edition](https://github.com/sugarkubes/generators/blob/master/logo.png?raw=true) 11 | 12 | 13 | ## Install 14 | 15 | ```sh 16 | npm i -g sugar-generate 17 | ``` 18 | 19 | ## Prereqs 20 | 21 | - have mongodb [installed and running](https://treehouse.github.io/installation-guides/mac/mongo-mac.html)! 22 | - have nodejs installed 23 | 24 | if you're running mongodb locally and using the docker container, make sure to start mongo using 25 | 26 | ```sh 27 | sudo mongod --bind_ip_all 28 | ``` 29 | 30 | otherwise you wont be able to connect to mongo from docker. 31 | 32 | ## Getting Started 33 | 34 | 1. Create your schema. It could be as simple as: 35 | 36 | ```json 37 | { 38 | "schema": { 39 | "name": { 40 | "type": "String", 41 | "default": "" 42 | }, 43 | "isDead": { 44 | "type": "Boolean", 45 | "default": false 46 | }, 47 | "age": { 48 | "type": "Number", 49 | "default": false 50 | } 51 | }, 52 | "statics": {} 53 | } 54 | ``` 55 | 56 | You can also provide multiple schemas like the following 57 | 58 | ```json 59 | [ 60 | { 61 | "name": "user", 62 | "schema": { 63 | "name": { 64 | "type": "String", 65 | "default": "" 66 | }, 67 | "email": { 68 | "type": "String", 69 | "trim": true, 70 | "required": true, 71 | "unique": true, 72 | "immutable": true 73 | }, 74 | "intro": { 75 | "type": "Boolean", 76 | "default": false 77 | }, 78 | "sub": { 79 | "one": { 80 | "type": "String", 81 | "trim": true, 82 | "required": true 83 | }, 84 | "two": { 85 | "type": "String", 86 | "required": true 87 | }, 88 | "three": { 89 | "type": "Number" 90 | } 91 | }, 92 | "role": { 93 | "type": "String", 94 | "enum": ["user", "maker"], 95 | "default": "user" 96 | } 97 | }, 98 | "statics": { 99 | "statuses": ["created", "under_review", "listed", "deleted"], 100 | "status": { 101 | "active": "active", 102 | "inactive": "inactive", 103 | "deleted": "deleted" 104 | } 105 | } 106 | }, 107 | { 108 | "name": "team", 109 | "schema": { 110 | "name": { 111 | "type": "String", 112 | "default": "" 113 | }, 114 | "users": { 115 | "type": "ObjectId", 116 | "ref": "Users" 117 | } 118 | } 119 | } 120 | ] 121 | ``` 122 | 123 | save this to **monkey.json** 124 | 125 | 2. generate your api 126 | 127 | ```sh 128 | sugar-generate \ 129 | --schema ./monkey.json \ 130 | --destination ./my-monkeys 131 | ``` 132 | or the short version 133 | 134 | ```sh 135 | sugar-generate \ 136 | -s ./monkey.json \ 137 | -d ./my-monkeys 138 | ``` 139 | 140 | **Note this is slightly different than previous versions. The schemas require a name now.** 141 | 142 | 3. Run or build the docker container and visit [http://localhost:3000](http://localhost:3000) 143 | 144 | ```sh 145 | cd /my-monkeys 146 | npm i 147 | npm start 148 | # Or build the docker container! 149 | docker build -t myMonkeys:0.1.0 . 150 | ``` 151 | 152 | ![running](https://github.com/sugarkubes/generators/blob/master/start.png?raw=true) 153 | ![oas docs](https://github.com/sugarkubes/generators/blob/master/monkey.png?raw=true) 154 | 155 | ## Features 🙉 156 | - Generates simple Nodejs code 157 | - Graphql and Rest out of the box 158 | - Uses Mongodb with Mongoose ORM 159 | - Easy to build / deploy 160 | - Dockerfile included 161 | - supports multiple schemas 162 | - Generates CRUD APIs 163 | - create 164 | - get (many, with pagination; supports search, sort, filter, pagination out of the box) 165 | - getOne 166 | - update 167 | - delete 168 | - Generates GraphQL apis for both query and mutation 169 | 170 | ## What it's good at 🙊 171 | 172 | - Generating an initial API 173 | - Microservice oriented 174 | - Ready to deploy (build with docker => deploy) 175 | 176 | ## What it's not good at (yet) 🙈 177 | 178 | - idempotent changes (i.e. it doesn't know if you wrote code in there or changed things around) 179 | - working with modified code 180 | - populating table joins 181 | - custom actions inside controller functions 182 | 183 | 184 | ## TODO 185 | 186 | 187 | ### API 188 | - ~~basic generator rest tests~~ 189 | - graphql tests 190 | - other databases? 191 | - **your ideas?** 192 | - middleware for auth, token validation, etc. 193 | 194 | 195 | ## Graphql support 196 | 197 | ![graphql mutation](https://github.com/sugarkubes/generators/blob/master/graphql-mutation.png?raw=true) 198 | 199 | ![graphql schema](https://github.com/sugarkubes/generators/blob/master/graphql-schema.png?raw=true) 200 | 201 | 202 | graphql is supported and gets created by default so you can choose between rest and graphql 203 | 204 | Graphql is on [http://localhost:3000/graphql](http://localhost:3000/graphql) 205 | 206 | 207 | 208 | ## Generated project structure 209 | 210 | . 211 | ├── configs # Config File 212 | ├── connection # DB Connections (mongo, redis) 213 | ├── controller # Controllers 214 | │ ├── # Functions (one file, one function) create, delete, update, get, getOne 215 | ├── models # DB Models 216 | ├── router # Endpoint Routes 217 | ├── tests # Single Test File 218 | 219 | ## Generated tests 220 | 221 | ** WARNING ** running the tests will pull the config file from **configs/config.json** and clear the DB 222 | 223 | ```sh 224 | npm run test 225 | ``` 226 | -------------------------------------------------------------------------------- /README.app.md: -------------------------------------------------------------------------------- 1 | # Instantly Generate React Components 🔥 2 | 3 | [![Follow on Twitter](https://img.shields.io/twitter/follow/andrewpierno.svg?label=follow)](https://twitter.com/andrewpierno) 4 | [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/sugarkubes/generators.svg)](http://isitmaintained.com/project/sugarkubes/generators "Average time to resolve an issue") 5 | [![Percentage of issues still open](http://isitmaintained.com/badge/open/sugarkubes/generators.svg)](http://isitmaintained.com/project/sugarkubes/generators "Percentage of issues still open") 6 | [![npm package](https://img.shields.io/npm/v/sugar-generate/latest.svg)](https://www.npmjs.com/package/sugar-generate) 7 | [![NPM Downloads](https://img.shields.io/npm/dt/sugar-generate.svg?style=flat)](https://npmcharts.com/compare/sugar-generate?minimal=true) 8 | 9 | 10 | ![Sugar Generator - API Edition](https://github.com/sugarkubes/generators/blob/master/logo.png?raw=true) 11 | 12 | 13 | ## Install 14 | 15 | ```sh 16 | npm i -g sugar-generate 17 | ``` 18 | 19 | ## Prereqs 20 | 21 | - have mongodb [installed and running](https://treehouse.github.io/installation-guides/mac/mongo-mac.html)! 22 | - have nodejs installed 23 | 24 | if you're running mongodb locally and using the docker container, make sure to start mongo using 25 | 26 | ```sh 27 | sudo mongod --bind_ip_all 28 | ``` 29 | otherwise you wont be able to connect to mongo from docker. 30 | 31 | ## Getting Started 32 | 33 | ```sh 34 | cd /my-monkeys 35 | npm i 36 | npm start 37 | # Or build the docker container! 38 | docker build -t myMonkeys:0.1.0 . 39 | ``` 40 | 41 | ![running](https://github.com/sugarkubes/generators/blob/master/start.png?raw=true) 42 | ![oas docs](https://github.com/sugarkubes/generators/blob/master/monkey.png?raw=true) 43 | 44 | ## Features 🙉 45 | - Generates simple Nodejs code 46 | - Graphql and Rest out of the box 47 | - Uses Mongodb with Mongoose ORM 48 | - Easy to build / deploy 49 | - Dockerfile included 50 | - supports multiple schemas 51 | - Generates CRUD APIs 52 | - create 53 | - get (many, with pagination; supports search, sort, filter, pagination out of the box) 54 | - getOne 55 | - update 56 | - delete 57 | - Generates GraphQL apis for both query and mutation 58 | 59 | ## What it's good at 🙊 60 | 61 | - Generating an initial API 62 | - Microservice oriented 63 | - Ready to deploy (build with docker => deploy) 64 | 65 | ## What it's not good at (yet) 🙈 66 | 67 | - idempotent changes (i.e. it doesn't know if you wrote code in there or changed things around) 68 | - working with modified code 69 | - populating table joins 70 | - custom actions inside controller functions 71 | 72 | 73 | ## TODO 74 | 75 | 76 | ### API 77 | - ~~basic generator rest tests~~ 78 | - graphql tests 79 | - other databases? 80 | - **your ideas?** 81 | - middleware for auth, token validation, etc. 82 | 83 | 84 | ## Graphql support 85 | 86 | ![graphql mutation](https://github.com/sugarkubes/generators/blob/master/graphql-mutation.png?raw=true) 87 | 88 | ![graphql schema](https://github.com/sugarkubes/generators/blob/master/graphql-schema.png?raw=true) 89 | 90 | 91 | graphql is supported and gets created by default so you can choose between rest and graphql 92 | 93 | Graphql is on [http://localhost:3000/graphql](http://localhost:3000/graphql) 94 | 95 | 96 | 97 | ## Generated project structure 98 | 99 | . 100 | ├── configs # Config File 101 | ├── connection # DB Connections (mongo, redis) 102 | ├── controller # Controllers 103 | │ ├── # Functions (one file, one function) create, delete, update, get, getOne 104 | ├── models # DB Models 105 | ├── router # Endpoint Routes 106 | ├── tests # Single Test File 107 | 108 | ## Generated tests 109 | 110 | ** WARNING ** running the tests will pull the config file from **configs/config.json** and clear the DB 111 | 112 | ```sh 113 | npm run test 114 | ``` 115 | -------------------------------------------------------------------------------- /README.dev: -------------------------------------------------------------------------------- 1 | # Dev Notes 2 | 3 | ```sh 4 | NODE_ENV=dev nodemon index.js \ 5 | -s ./sample-schemas/auth.json \ 6 | -d ../auth 7 | ``` 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rest/GraphQL APIs + React Components Generator 🔥 2 | 3 | [![Buy Ligit License](https://s3.us-west-1.wasabisys.com/public.sugarkubes/ligit_embed.svg)](https://ligit.dev) 4 | [![Follow on Twitter](https://img.shields.io/twitter/follow/andrewpierno.svg?label=follow)](https://twitter.com/andrewpierno) 5 | [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/sugarkubes/generators.svg)](http://isitmaintained.com/project/sugarkubes/generators "Average time to resolve an issue") 6 | [![Percentage of issues still open](http://isitmaintained.com/badge/open/sugarkubes/generators.svg)](http://isitmaintained.com/project/sugarkubes/generators "Percentage of issues still open") 7 | [![npm package](https://img.shields.io/npm/v/sugar-generate/latest.svg)](https://www.npmjs.com/package/sugar-generate) 8 | [![NPM Downloads](https://img.shields.io/npm/dt/sugar-generate.svg?style=flat)](https://npmcharts.com/compare/sugar-generate?minimal=true) 9 | 10 | 11 | ![Sugar Generator - API Edition](https://github.com/sugarkubes/generators/blob/master/logo.png?raw=true) 12 | 13 | [![App Demo](https://img.youtube.com/vi/E7_ABK7nZT8/0.jpg)](https://www.youtube.com/watch?v=E7_ABK7nZT8) 14 | 15 | 16 | This project is sponsored by 17 | 18 | [![SponsoredBy.dev](https://api.sponsoredby.dev/img/d8c4307b-ba42-400e-b475-9487c76d15c8.png)](https://api.sponsoredby.dev/link/d8c4307b-ba42-400e-b475-9487c76d15c8) 19 | 20 | # Quick Start 21 | 22 | 23 | 1. Install the npm module 24 | 25 | ```sh 26 | # install 27 | npm i -g sugar-generate 28 | ``` 29 | 30 | 2. Create a json schema save, this to **monkey.json** 31 | 32 | 33 | ```json 34 | { 35 | "name": "monkey", 36 | "schema": { 37 | "name": { 38 | "type": "String", 39 | "default": "" 40 | }, 41 | "alive": { 42 | "type": "Boolean", 43 | "default": false 44 | }, 45 | "age": { 46 | "type": "Number", 47 | "default": false 48 | } 49 | } 50 | } 51 | ``` 52 | 53 | 3. Generate your api and app 54 | 55 | ```sh 56 | sugar-generate \ 57 | --schema monkey.json \ 58 | --destination ./my-monkeys 59 | ``` 60 | 61 | Boom, you now have: 62 | 63 | API: 64 | - GraphQL API 65 | - REST API 66 | - Working Tests 67 | 68 | APP: 69 | - React create item form 70 | - React table that supports 71 | - search 72 | - sort 73 | - filter 74 | - pagination 75 | - edit item 76 | - create item 77 | 78 | 79 | ### Start the API 80 | 81 | ```sh 82 | cd ./my-monkeys/api 83 | npm i 84 | npm run start 85 | 86 | # http://localhost:7777 87 | ``` 88 | 89 | ### Start the APP 90 | 91 | ```sh 92 | cd ./my-monkeys/app 93 | npm i 94 | npm run dev 95 | 96 | # http://localhost:3000 97 | ``` 98 | 99 | ### Behold Magic 100 | 101 | A fully functioning react table and form with searching sorting filtering, editing, adding, global search, download, and refresh. 102 | 103 | ![SugarKubes Generated App](https://github.com/sugarkubes/generators/blob/master/table-component.png?raw=true) 104 | 105 | # Links 106 | 107 | [GraphQL is on localhost:7777/graphql](http://localhost:7777/graphql) 108 | 109 | [Swagger is on localhost:7777](http://localhost:7777) 110 | 111 | [APP is on localhost:3000](http://localhost:3000) 112 | 113 | [API is on localhost:7777](http://localhost:3000) 114 | 115 | # Documentation 116 | 117 | [API Documentation (generated back end)](https://github.com/sugarkubes/generators/wiki/API) 118 | 119 | 120 | [App Documentation (generated front end)](https://github.com/sugarkubes/generators/wiki/APP) 121 | 122 | ## Experimental Embeddable Components 123 | 124 | **What if you could remotely update your components without having to push new code?** 125 | 126 | Thats one question we're exploring with the experimental embeddable react components. There are of course cool ways to serve single pages as serverless functions but what's cooler would be a way for even non-technical people to update a database schema and a form or table in real time without writing any code. 127 | 128 | 129 | - Each component comes out in an embeddable format under */embed* 130 | - TBD - How to easily deploy these and use them. 131 | - 132 | 133 | 134 | ## Updates 135 | 136 | - 8/5/19 support for mongo arrays in documents 137 | -------------------------------------------------------------------------------- /api/code/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | port: 7777, 3 | db: { 4 | mongoURL: "mongodb://localhost:27017/sugar", 5 | mongoOptions: { useNewUrlParser: true } 6 | }, 7 | redisConfig: { 8 | host: "localhost", 9 | port: 6379, 10 | password: "" 11 | }, 12 | jwt_secret: "sugar-secret", 13 | basicAuth: { 14 | username: "sugar", 15 | password: "kubes" 16 | }, 17 | env: "local" 18 | }; 19 | -------------------------------------------------------------------------------- /api/code/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | config: require('./config'), 3 | }; 4 | -------------------------------------------------------------------------------- /api/config/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | makeConfig: require('./makeConfig'), 3 | }; 4 | -------------------------------------------------------------------------------- /api/config/makeConfig.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { config } = require('../code'); 3 | 4 | module.exports = ({ schema, logging, destination }) => { 5 | // schema = require(schema); 6 | const configsFolder = `${destination}/configs`; 7 | const file = `${configsFolder}/config.json` 8 | // if (logging) console.log('checking configs '); 9 | if (!fs.existsSync(configsFolder)) { 10 | // if (logging) console.log('creating configs '); 11 | fs.mkdirSync(configsFolder); 12 | } 13 | if (!fs.existsSync(file)) { 14 | // if (logging) console.log('creating configs/configs.json'); 15 | fs.writeFileSync(file, JSON.stringify(config, null, 2)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /api/controller/create.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | 4 | module.exports = ({ schema, logging, destination, name }) => { 5 | const action = 'create'; 6 | const { uppercase, createValidationCode, sugarGenerated } = require('../utils'); 7 | if (logging) console.log(` API => REST => CREATE ${name}`); 8 | const controllerSubFolder = `${destination}/controller/${name}`; 9 | const createFile = `${controllerSubFolder}/create.js`; 10 | 11 | let code = []; 12 | const top = [ 13 | sugarGenerated(), 14 | `const ${uppercase(name)} = require("../../models/${uppercase(name)}");`, 15 | // `const { userCanApiKey } = require('../../configs/config');`, 16 | // `const userCan = require('../../user-can')(userCanApiKey);`, 17 | ]; 18 | 19 | const swagger = [ 20 | "/*", 21 | `* @oas [post] /${name}`, 22 | `* summary: "create a ${name}"`, 23 | `* tags: ["${name}"]`, 24 | `* requestBody:`, 25 | `* description: ${uppercase(name)} - **Create** `, 26 | `* required: true`, 27 | `* content:`, 28 | `* application/json:`, 29 | `* schema:`, 30 | `* $ref: '#/components/schemas/${uppercase(name)}'`, 31 | `* responses:`, 32 | `* "200":`, 33 | `* description: "create a ${name}"`, 34 | `* schema:`, 35 | `* type: "${uppercase(name)}"`, 36 | "*/", 37 | ]; 38 | const schemaKeys = Object.keys(schema.schema); 39 | const validate = createValidationCode(schema.schema, name); 40 | const func = [ 41 | `module.exports = async (req, res) => {`, 42 | ` try {`, 43 | ` const { ${schemaKeys.join(', ')}} = req.body;`, 44 | ` const new${uppercase(name)} = {};`, 45 | ]; 46 | const end = [ 47 | ` `, 48 | ` } catch (e) {`, 49 | ` console.error('Create => ${name}', e);`, 50 | ` return res.status(500).json({ error: e.message ? e.message : e });`, 51 | ` }`, 52 | `};`, 53 | ]; 54 | const permissions = [ 55 | // ``, 56 | // `// @TODO Permissions`, 57 | // `const permission = userCan('${action}', '${name}', { user: req.user, body: req.body, params: req.params, query: req.query });`, 58 | // `if (!permission) throw new Error('Permission denied for userCan ${action} ${name}');`, 59 | // `` 60 | ]; 61 | const safeArea = [ 62 | `// @TODO handle safe area. Should be idempotent.`, 63 | ``, 64 | `// maybe with @sugar-safe-start`, 65 | `// @sugar-safe-end`, 66 | `` 67 | ]; 68 | const save = [ 69 | `// save`, 70 | `const created = await ${uppercase(name)}.create(new${uppercase(name)});`, 71 | `return res.json({ ${name}: created });` 72 | ]; 73 | code = code.concat(top, swagger, func, validate, permissions, safeArea, save, end); 74 | const pretty = beautify(code.join('\n'), { indent_size: 2, space_in_empty_paren: true }); 75 | fs.writeFileSync(createFile, pretty); 76 | }; 77 | -------------------------------------------------------------------------------- /api/controller/delete.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | 4 | module.exports = ({ schema, logging, destination, name }) => { 5 | const action = 'delete'; 6 | const { uppercase, createUpdateCode, sugarGenerated } = require('../utils'); 7 | if (logging) console.log(` API => REST => DELETE ${name}`); 8 | const controllerSubFolder = `${destination}/controller/${name}`; 9 | const createFile = `${controllerSubFolder}/delete${uppercase(name)}.js`; 10 | 11 | let code = []; 12 | const top = [ 13 | sugarGenerated(), 14 | `const ${uppercase(name)} = require("../../models/${uppercase(name)}");`, 15 | // `const { userCanApiKey } = require('../../configs/config');`, 16 | // `const userCan = require('../../user-can')(userCanApiKey);`, 17 | ]; 18 | 19 | const swagger = [ 20 | "/*", 21 | `* @oas [delete] /${name}/{id}`, 22 | `* summary: "delete a ${name}"`, 23 | `* tags: ["${name}"]`, 24 | `* parameters:`, 25 | `* - name: 'id'`, 26 | `* in: 'path'`, 27 | `* description: id of the ${name}`, 28 | `* schema:`, 29 | `* type: 'string'`, 30 | `* example: "123456"`, 31 | `* responses:`, 32 | `* "200":`, 33 | `* description: "delete a ${name}"`, 34 | `* schema:`, 35 | `* type: "${uppercase(name)}"`, 36 | "*/", 37 | ]; 38 | const schemaKeys = Object.keys(schema.schema); 39 | // const validate = createUpdateCode(schema.schema, name); 40 | const validate = []; 41 | 42 | const func = [ 43 | `module.exports = async (req, res) => {`, 44 | ` try {`, 45 | ` const { id } = req.params;`, 46 | ` const existing${uppercase(name)} = await ${uppercase(name)}.findOne({ _id: id });`, 47 | ` if (!existing${uppercase(name)}) throw new Error('${name} not found.');`, 48 | ]; 49 | const end = [ 50 | ` `, 51 | ` } catch (e) {`, 52 | ` console.error('Delete => ${name}', e);`, 53 | ` return res.status(500).json({ error: e.message ? e.message : e });`, 54 | ` }`, 55 | `};`, 56 | ]; 57 | const permissions = [ 58 | // ``, 59 | // `// @TODO Permissions`, 60 | // `const permission = userCan('${action}', '${name}', { user: req.user, body: req.body, params: req.params, query: req.query });`, 61 | // `if (!permission) throw new Error('Permission denied for userCan ${action} ${name}');`, 62 | // `` 63 | ]; 64 | const safeArea = [ 65 | `// @TODO handle safe area. Should be idempotent.`, 66 | ``, 67 | `// maybe with @sugar-safe-start`, 68 | `// @sugar-safe-end`, 69 | `` 70 | ]; 71 | const save = [ 72 | `// save`, 73 | `const deleted = await existing${uppercase(name)}.delete();`, 74 | `return res.json({ ${name}: deleted });` 75 | ]; 76 | code = code.concat(top, swagger, func, validate, permissions, safeArea, save, end); 77 | const pretty = beautify(code.join('\n'), { indent_size: 2, space_in_empty_paren: true }); 78 | fs.writeFileSync(createFile, pretty); 79 | }; 80 | -------------------------------------------------------------------------------- /api/controller/get.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | 4 | module.exports = ({ schema, logging, destination, name }) => { 5 | const action = 'get'; 6 | const { getSearchCode, uppercase, getValidationCode, sugarGenerated, extraParams, getSchemaQueryDefinitions } = require('../utils'); 7 | if (logging) console.log(` API => REST => GET ${name}`); 8 | const controllerSubFolder = `${destination}/controller/${name}`; 9 | const createFile = `${controllerSubFolder}/get.js`; 10 | 11 | let code = []; 12 | const top = [ 13 | sugarGenerated(), 14 | `const ${uppercase(name)} = require("../../models/${uppercase(name)}");`, 15 | // `const { userCanApiKey } = require('../../configs/config');`, 16 | // `const userCan = require('../../user-can')(userCanApiKey);`, 17 | ]; 18 | let swagger = [ 19 | "/*", 20 | `* @oas [get] /${name}s`, 21 | `* summary: "get ${name}s"`, 22 | `* tags: ["${name}"]`, 23 | `* parameters: `, 24 | `* - in: query`, 25 | `* name: page`, 26 | `* description: page`, 27 | `* schema:`, 28 | `* type: integer`, 29 | `* - in: query`, 30 | `* name: limit`, 31 | `* description: The numbers of items to return`, 32 | `* schema:`, 33 | `* type: integer`, 34 | `* - in: query`, 35 | `* name: filters`, 36 | `* description: Filters to search on specific 'columns'`, 37 | `* style: deepObject`, 38 | `* schema:`, 39 | `* type: object`, 40 | `* example: 'stringified array [{"column":{"title":"Name","field":"name","type":"…Sort":"asc","id":0}},"operator":"=","value":"1"}]'`, 41 | `* - in: query`, 42 | `* name: orderBy`, 43 | `* style: deepObject`, 44 | `* description: object containing how to sort the items`, 45 | `* schema:`, 46 | `* type: object`, 47 | `* example: { "field": "asc", "test": -1, "field2": "desc" }`, 48 | `* - in: query`, 49 | `* name: select`, 50 | `* description: object containing fields want to be returned`, 51 | `* style: deepObject`, 52 | `* schema:`, 53 | `* type: object`, 54 | `* example: { "first_name": 1 }`, 55 | ]; 56 | 57 | swagger = swagger.concat(getSchemaQueryDefinitions(schema.schema)); 58 | swagger = swagger.concat([ 59 | // `* requestBody:`, 60 | // `* description: ${uppercase(name)} - **GET** `, 61 | // `* required: true`, 62 | // `* content:`, 63 | // `* application/json:`, 64 | // `* schema:`, 65 | // `* $ref: '#/components/schemas/Extended${uppercase(name)}'`, 66 | `* responses:`, 67 | `* "200":`, 68 | `* description: "get ${name}s"`, 69 | `* schema:`, 70 | `* type: "${uppercase(name)}"`, 71 | "*/", 72 | ]); 73 | const schemaKeys = Object.keys(schema.schema); 74 | const paginateValidation = getValidationCode(schema.schema, name); 75 | const extraKeys = Object.keys(extraParams); 76 | 77 | 78 | const func = [ 79 | `module.exports = async (req, res) => {`, 80 | ` try {`, 81 | ` let { ${extraKeys.join(', ')}} = req.query;`, 82 | ` const { ${schemaKeys.join(', ')}} = req.query;`, 83 | //` console.log('req query ', req.query);`, 84 | ` // The model query`, 85 | ` const find = {};`, 86 | ` let parsedFilters = null;`, 87 | ` // pagination object (search, sort, filter, etc); 88 | const where = {}; 89 | if (filters && filters !== '[]') { 90 | parsedFilters = JSON.parse(filters); 91 | 92 | parsedFilters.forEach((f) => { 93 | let regexValue = {}; 94 | if (f.column.type === 'boolean') { 95 | regexValue = f.value === 'checked' ? true : false; 96 | } else if (Array.isArray(f.value) && f.value.length > 0) { 97 | if (f.column.type === 'string') { 98 | regexValue = { $in: [] }; 99 | f.value.forEach((val) => { 100 | regexValue.$in.push(val) 101 | }); 102 | } else { 103 | regexValue = { $or: [] }; 104 | f.value.forEach((val) => { 105 | regexValue.$or.push({ $eq: val }); 106 | }); 107 | } 108 | } else if (f.column.type === 'numeric' || f.column.type === 'number') { 109 | regexValue = { $eq: f.value }; 110 | } else { 111 | regexValue = { $regex: new RegExp(f.value, "ig")}; 112 | } 113 | if (JSON.stringify(regexValue) !== '{}') find[f.column.field] = regexValue; 114 | }); 115 | `, 116 | ` }`, 117 | ` 118 | 119 | // search 120 | if (search) { 121 | ${getSearchCode(schema)}; 122 | } 123 | `, 124 | ]; 125 | const end = [ 126 | ` `, 127 | ` } catch (e) {`, 128 | ` console.error('GET => ${name}', e);`, 129 | ` return res.status(500).json({ error: e.message ? e.message : e });`, 130 | ` }`, 131 | `};`, 132 | ]; 133 | const permissions = [ 134 | // ``, 135 | // `// @TODO Permissions`, 136 | // `const permission = userCan('${action}', '${name}', { user: req.user, body: req.body, params: req.params, query: req.query });`, 137 | // `if (!permission) throw new Error('Permission denied for userCan ${action} ${name}');`, 138 | // `` 139 | ]; 140 | const safeArea = [ 141 | `// @TODO handle safe area. Should be idempotent.`, 142 | ``, 143 | `// maybe with @sugar-safe-start`, 144 | `// @sugar-safe-end`, 145 | `` 146 | ]; 147 | const save = [ 148 | `// save`, 149 | // `console.log('find ', find);`, 150 | // `console.log('where', where);`, 151 | `const ${name} = await ${uppercase(name)}.paginate(find, where); // @TODO populate: ''`, 152 | `return res.json({ ${name}s: ${name} });` 153 | ]; 154 | code = code.concat(top, swagger, func, paginateValidation, permissions, safeArea, save, end); 155 | const pretty = beautify(code.join('\n'), { indent_size: 2, space_in_empty_paren: true }); 156 | fs.writeFileSync(createFile, pretty); 157 | }; 158 | -------------------------------------------------------------------------------- /api/controller/getOne.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | 4 | module.exports = ({ schema, logging, destination, name }) => { 5 | const action = 'getOne'; 6 | const { uppercase, sugarGenerated } = require('../utils'); 7 | if (logging) console.log(` API => REST => GET ONE ${name}`); 8 | const controllerSubFolder = `${destination}/controller/${name}`; 9 | const createFile = `${controllerSubFolder}/getOne.js`; 10 | 11 | let code = []; 12 | const top = [ 13 | sugarGenerated(), 14 | `const ${uppercase(name)} = require("../../models/${uppercase(name)}");`, 15 | // `const { userCanApiKey } = require('../../configs/config');`, 16 | // `const userCan = require('../../user-can')(userCanApiKey);`, 17 | ]; 18 | 19 | const swagger = [ 20 | "/*", 21 | `* @oas [get] /${name}/{id}`, 22 | `* summary: "get one ${name}"`, 23 | `* tags: ["${name}"]`, 24 | `* parameters:`, 25 | `* - name: 'id'`, 26 | `* in: 'path'`, 27 | `* description: id of the ${name}`, 28 | `* schema:`, 29 | `* type: 'string'`, 30 | `* example: "123456"`, 31 | `* responses:`, 32 | `* "200":`, 33 | `* description: "get one ${name}"`, 34 | `* schema:`, 35 | `* type: "${uppercase(name)}"`, 36 | "*/", 37 | ]; 38 | const func = [ 39 | `module.exports = async (req, res) => {`, 40 | ` try {`, 41 | ` const { id } = req.params;`, 42 | ` const existing${uppercase(name)} = await ${uppercase(name)}.findOne({ _id: id });`, 43 | ` if (!existing${uppercase(name)}) throw new Error('${name} not found.');`, 44 | ]; 45 | const end = [ 46 | ` `, 47 | ` } catch (e) {`, 48 | ` console.error('GetOne => ${name}', e);`, 49 | ` return res.status(500).json({ error: e.message ? e.message : e });`, 50 | ` }`, 51 | `};`, 52 | ]; 53 | const permissions = [ 54 | // ``, 55 | // `// @TODO Permissions`, 56 | // `const permission = userCan('${action}', '${name}', { user: req.user, body: req.body, params: req.params, query: req.query });`, 57 | // `if (!permission) throw new Error('Permission denied for userCan ${action} ${name}');`, 58 | // `` 59 | ]; 60 | const safeArea = [ 61 | `// @TODO handle safe area. Should be idempotent.`, 62 | ``, 63 | `// maybe with @sugar-safe-start`, 64 | `// @sugar-safe-end`, 65 | `` 66 | ]; 67 | const save = [ 68 | `return res.json({ ${name}: existing${uppercase(name)} });` 69 | ]; 70 | code = code.concat(top, swagger, func, permissions, safeArea, save, end); 71 | const pretty = beautify(code.join('\n'), { indent_size: 2, space_in_empty_paren: true }); 72 | fs.writeFileSync(createFile, pretty); 73 | }; 74 | -------------------------------------------------------------------------------- /api/controller/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | makeAPI: require('./makeAPI'), 3 | create: require('./create'), 4 | get: require('./get'), 5 | update: require('./update'), 6 | delete: require('./delete'), 7 | indexFile: require('./indexFile'), 8 | }; 9 | -------------------------------------------------------------------------------- /api/controller/indexFile.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | 4 | module.exports = ({ schema, logging, destination, name }) => { 5 | if (logging) console.log(` API => REST => INDEX ${name}`); 6 | const { uppercase } = require('../utils'); 7 | const controllerSubFolder = `${destination}/controller/${name}`; 8 | const createFile = `${controllerSubFolder}/index.js`; 9 | 10 | const code = ` 11 | const create${uppercase(name)} = require('./create'); 12 | const get${uppercase(name)} = require('./get'); 13 | const getOne${uppercase(name)} = require('./getOne'); 14 | const update${uppercase(name)} = require('./update'); 15 | const delete${uppercase(name)} = require('./delete${uppercase(name)}'); 16 | 17 | module.exports = { 18 | create${uppercase(name)}, 19 | get${uppercase(name)}, 20 | getOne${uppercase(name)}, 21 | update${uppercase(name)}, 22 | delete${uppercase(name)}, 23 | }; 24 | 25 | `; 26 | const pretty = beautify(code, { indent_size: 2, space_in_empty_paren: true }); 27 | fs.writeFileSync(createFile, pretty); 28 | }; 29 | -------------------------------------------------------------------------------- /api/controller/makeAPI.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | 4 | const doMakeApi = ({ schema, logging, destination, name }) => { 5 | const create = require('./create'); 6 | const get = require('./get'); 7 | const getOne = require('./getOne'); 8 | const update = require('./update'); 9 | const remove = require('./delete'); 10 | const indexFile = require('./indexFile'); 11 | 12 | const controllerSubFolder = `${destination}/controller/${name}`; 13 | 14 | if (!fs.existsSync(controllerSubFolder)) { 15 | fs.mkdirSync(controllerSubFolder); 16 | } 17 | 18 | create({ schema, logging, destination, name }); 19 | get({ schema, logging, destination, name }); 20 | getOne({ schema, logging, destination, name }); 21 | update({ schema, logging, destination, name }); 22 | remove({ schema, logging, destination, name }); 23 | indexFile({ schema, logging, destination, name }); 24 | }; 25 | 26 | module.exports = ({ schema, logging, destination }) => { 27 | 28 | const controllerFolder = `${destination}/controller`; 29 | if (!fs.existsSync(controllerFolder)) { 30 | fs.mkdirSync(controllerFolder); 31 | } 32 | 33 | schema = require(schema); // eslint-disable-line 34 | if (Array.isArray(schema)) { 35 | schema.forEach((_schema) => { 36 | console.log("SCHEMA =>", _schema.name); 37 | doMakeApi({ schema: _schema, logging, destination, name: _schema.name }); 38 | }); 39 | } else { 40 | const name = schema.name; // eslint-disable-line 41 | console.log("SCHEMA =>", name); 42 | doMakeApi({ schema, logging, destination, name }); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /api/controller/update.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | 4 | module.exports = ({ schema, logging, destination, name }) => { 5 | const action = 'update'; 6 | const { uppercase, createUpdateCode, sugarGenerated } = require('../utils'); 7 | if (logging) console.log(` API => REST => UPDATE ${name}`); 8 | const controllerSubFolder = `${destination}/controller/${name}`; 9 | const createFile = `${controllerSubFolder}/update.js`; 10 | 11 | let code = []; 12 | const top = [ 13 | sugarGenerated(), 14 | `const ${uppercase(name)} = require("../../models/${uppercase(name)}");`, 15 | // `const { userCanApiKey } = require('../../configs/config');`, 16 | // `const userCan = require('../../user-can')(userCanApiKey);`, 17 | ]; 18 | 19 | const swagger = [ 20 | "/*", 21 | `* @oas [put] /${name}/{id}`, 22 | `* summary: "update a ${name}"`, 23 | `* tags: ["${name}"]`, 24 | `* parameters:`, 25 | `* - name: 'id'`, 26 | `* in: 'path'`, 27 | `* description: id of the ${name}`, 28 | `* schema:`, 29 | `* type: 'string'`, 30 | `* example: "123456"`, 31 | `* requestBody:`, 32 | `* description: ${uppercase(name)} - **Update** `, 33 | `* required: true`, 34 | `* content:`, 35 | `* application/json:`, 36 | `* schema:`, 37 | `* $ref: '#/components/schemas/${uppercase(name)}'`, 38 | `* responses:`, 39 | `* "200":`, 40 | `* description: "update a ${name}"`, 41 | `* schema:`, 42 | `* type: "${uppercase(name)}"`, 43 | "*/", 44 | ]; 45 | const schemaKeys = Object.keys(schema.schema); 46 | const validate = createUpdateCode(schema.schema, name); 47 | const func = [ 48 | `module.exports = async (req, res) => {`, 49 | ` try {`, 50 | ` const { ${schemaKeys.join(', ')}} = req.body;`, 51 | ` const { id } = req.params;`, 52 | ` const existing${uppercase(name)} = await ${uppercase(name)}.findOne({ _id: id });`, 53 | ` if (!existing${uppercase(name)}) throw new Error('${name} not found.');`, 54 | ]; 55 | const end = [ 56 | ` `, 57 | ` } catch (e) {`, 58 | ` console.error('Update => ${name}', e);`, 59 | ` return res.status(500).json({ error: e.message ? e.message : e });`, 60 | ` }`, 61 | `};`, 62 | ]; 63 | const permissions = [ 64 | // ``, 65 | // `// @TODO Permissions`, 66 | // `const permission = userCan('${action}', '${name}', { user: req.user, body: req.body, params: req.params, query: req.query });`, 67 | // `if (!permission) throw new Error('Permission denied for userCan ${action} ${name}');`, 68 | // `` 69 | ]; 70 | const safeArea = [ 71 | `// @TODO handle safe area. Should be idempotent.`, 72 | ``, 73 | `// maybe with @sugar-safe-start`, 74 | `// @sugar-safe-end`, 75 | `` 76 | ]; 77 | const save = [ 78 | `// save`, 79 | `const updated = await existing${uppercase(name)}.save();`, 80 | `return res.json({ ${name}: updated });` 81 | ]; 82 | code = code.concat(top, swagger, func, validate, permissions, safeArea, save, end); 83 | const pretty = beautify(code.join('\n'), { indent_size: 2, space_in_empty_paren: true }); 84 | fs.writeFileSync(createFile, pretty); 85 | }; 86 | -------------------------------------------------------------------------------- /api/graphql/createIndex.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | 4 | module.exports = ({ destination, flavor }) => { 5 | const modelFolder = `${destination}/graphql`; 6 | const indexFile = `${modelFolder}/index.js`; 7 | const code = ` 8 | module.exports = { 9 | typeDefs: require('./typeDefs'), 10 | resolvers: require('./resolvers'), 11 | prepare: require('./prepare'), 12 | }; 13 | `; 14 | 15 | // if (logging) console.log('checking user-can'); 16 | if (!fs.existsSync(modelFolder)) { 17 | // if (logging) console.log('creating user-can'); 18 | fs.mkdirSync(modelFolder); 19 | } 20 | const pretty = beautify(code, { indent_size: 2, space_in_empty_paren: true }); 21 | fs.writeFileSync(indexFile, pretty); 22 | }; 23 | -------------------------------------------------------------------------------- /api/graphql/createResolvers.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | const { uppercase } = require('../utils'); 4 | 5 | module.exports = ({ destination, flavor, name }) => { 6 | const modelFolder = `${destination}/graphql`; 7 | const indexFile = `${modelFolder}/resolvers.js`; 8 | const code = ` 9 | const ${uppercase(name)} = require('../models/${name}'); 10 | const prepare = require('./prepare'); 11 | const { ObjectId } = require('mongodb'); 12 | 13 | module.exports = { 14 | Query: { 15 | ${name}: async (root, {_id}) => { 16 | // // @TODO Permissions, 17 | // if (!permission) throw new Error('Permission denied for userCan getOne ${name}');, 18 | return await ${uppercase(name)}.findOne({ _id }); 19 | }, 20 | ${name}s: async () => { 21 | // // @TODO Permissions, 22 | // const permission = userCan('get', '${name}', req.user, args);, 23 | // if (!permission) throw new Error('Permission denied for userCan get ${name}');, 24 | return await ${uppercase(name)}.find({}); 25 | }, 26 | }, 27 | Mutation: { 28 | create${uppercase(name)}: async (root, args, context, info) => { 29 | // // @TODO Permissions, 30 | // const permission = userCan('create', '${name}', req.user, args);, 31 | // if (!permission) throw new Error('Permission denied for userCan create ${name}');, 32 | const new${uppercase(name)} = await ${uppercase(name)}.create(args); 33 | return new${uppercase(name)}; 34 | }, 35 | update${uppercase(name)}: async (root, args, context, info) => { 36 | const argscopy = Object.assign({}, args); 37 | delete argscopy._id; 38 | let getOne = await ${uppercase(name)}.findOne({ _id: args._id }); 39 | 40 | Object.keys(args).forEach(key => getOne[key] = args[key]); 41 | // // @TODO Permissions, 42 | // const permission = userCan('update', '${name}', req.user, args);, 43 | // if (!permission) throw new Error('Permission denied for userCan update ${name}');, 44 | await getOne.save(); 45 | return getOne; 46 | }, 47 | delete${uppercase(name)}: async (root, args, context, info) => { 48 | const getOne = await ${uppercase(name)}.findOne({ _id: args._id }); 49 | // // @TODO Permissions, 50 | // const permission = userCan('delete', '${name}', req.user, args);, 51 | // if (!permission) throw new Error('Permission denied for userCan delete ${name}');, 52 | if (!getOne) throw new Error('not found') 53 | await getOne.delete(); 54 | return getOne; 55 | }, 56 | }, 57 | }; 58 | 59 | 60 | `; 61 | 62 | // if (logging) console.log('checking user-can'); 63 | if (!fs.existsSync(modelFolder)) { 64 | // if (logging) console.log('creating user-can'); 65 | fs.mkdirSync(modelFolder); 66 | } 67 | const pretty = beautify(code, { indent_size: 2, space_in_empty_paren: true }); 68 | fs.writeFileSync(indexFile, pretty); 69 | }; 70 | -------------------------------------------------------------------------------- /api/graphql/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | createIndex: require('./createIndex'), 3 | makeGraphql: require('./makeGraphql'), 4 | createResolvers: require('./createResolvers'), 5 | tcString: require('./tcString'), 6 | }; 7 | -------------------------------------------------------------------------------- /api/graphql/makeGraphql.js: -------------------------------------------------------------------------------- 1 | const createIndex = require('./createIndex'); 2 | const createResolvers = require('./createResolvers'); 3 | module.exports = async (args) => { 4 | await createIndex(args); 5 | await createResolvers(args); 6 | }; 7 | -------------------------------------------------------------------------------- /api/graphql/tcString.js: -------------------------------------------------------------------------------- 1 | module.exports = (name) => { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /api/makeApi.js: -------------------------------------------------------------------------------- 1 | const { makeConfig } = require('./config'); 2 | const { makeSwaggerModelDefinitions } = require('./swagger'); 3 | const { makeRouter } = require('./router'); 4 | const mkdirp = require('mkdirp'); 5 | const fs = require('fs'); 6 | 7 | const { 8 | serverIndex, 9 | // userCan, 10 | dockerFile, 11 | writePackageJSON, 12 | writeEslint, 13 | writeDockerIgnore, 14 | writeGitIgnore, 15 | readme 16 | } = require('./server'); 17 | const { 18 | makeConnection, 19 | makeSchema, 20 | makeModelIndex, 21 | } = require('./mongodb'); 22 | const { 23 | makeTests 24 | } = require('./tests'); 25 | 26 | const { makeAPI } = require('./controller'); 27 | 28 | module.exports = async (args) => { 29 | if (!fs.existsSync(args.destination)) { 30 | mkdirp.sync(args.destination); 31 | } else if (process.env.NODE_ENV !== 'dev') { 32 | return console.log(`uh oh, looks like there's something already at ${args.destination}`); // eslint-disable-line 33 | } 34 | await makeConfig(args); 35 | await makeConnection(args); 36 | await makeSchema(args); 37 | await makeModelIndex(args); 38 | await makeAPI(args); 39 | await makeSwaggerModelDefinitions(args); 40 | await makeRouter(args); 41 | await serverIndex(args); 42 | // await userCan(args); 43 | await dockerFile(args); 44 | await writePackageJSON(args); 45 | await writeEslint(args); 46 | await writeGitIgnore(args); 47 | await writeDockerIgnore(args); 48 | await readme(args); 49 | await makeTests(args); 50 | if (args.logging) console.log('all done 🚀'); // eslint-disable-line 51 | }; 52 | -------------------------------------------------------------------------------- /api/mongodb/connectionIndex.js: -------------------------------------------------------------------------------- 1 | module.exports = `module.exports = { 2 | mongo: require('./mongo'), 3 | redis: require('./redis'), 4 | }; 5 | ` 6 | -------------------------------------------------------------------------------- /api/mongodb/connectionMongo.js: -------------------------------------------------------------------------------- 1 | module.exports = ` 2 | const mongoose = require('mongoose'); 3 | const { db } = require('../configs/config'); 4 | const { mongoURL, mongoOptions } = db; 5 | mongoose.set('useCreateIndex', true); 6 | // Use native promises 7 | mongoose.Promise = Promise; 8 | 9 | // Initialize our database 10 | mongoose.connect(mongoURL, mongoOptions) 11 | .catch((e) => { 12 | console.error('mongoose error ', e.message); 13 | }); 14 | 15 | const database = mongoose.connection; 16 | database.on('error', () => ( 17 | setTimeout(() => { 18 | console.error('MONGO CONNECTION FAILED => trying again ', mongoURL); 19 | try { 20 | mongoose.connect(mongoURL, mongoOptions) 21 | .catch((e) => { 22 | console.error('mongoose error ', e.message); 23 | }); 24 | } catch (e) { 25 | console.error('MONGO CONNECTION e ', e.message); 26 | } 27 | }, 5000) 28 | )); 29 | database.once('open', () => { 30 | console.info('Mongo OKAY'); 31 | mongoose.connection.on('connected', () => { 32 | console.info('MongoDB event connected'); 33 | }); 34 | 35 | mongoose.connection.on('disconnected', () => { 36 | console.error('MongoDB event disconnected'); 37 | }); 38 | 39 | mongoose.connection.on('reconnected', () => { 40 | console.info('MongoDB event reconnected'); 41 | }); 42 | 43 | mongoose.connection.on('error', (err) => { 44 | console.error('MongoDB event error: ' + err); 45 | }); 46 | }); 47 | 48 | module.exports = database; 49 | ` 50 | -------------------------------------------------------------------------------- /api/mongodb/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | makeConnection: require('./makeConnection'), 3 | modelIndex: require('./modelIndex'), 4 | connectionMongo: require('./connectionMongo'), 5 | connectionIndex: require('./connectionIndex'), 6 | makeSchema: require('./makeSchema'), 7 | makeModelIndex: require('./makeModelIndex'), 8 | }; 9 | -------------------------------------------------------------------------------- /api/mongodb/makeConnection.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const connectionIndex = require('./connectionIndex'); 4 | const connectionMongo = require('./connectionMongo'); 5 | const connectionRedis = require('../redis/connectionRedis'); 6 | 7 | module.exports = ({ schema, logging, destination }) => { 8 | schema = require(schema); // eslint-disable-line 9 | const modelFolder = `${destination}/connection`; 10 | const mongoFile = `${modelFolder}/mongo.js`; 11 | const redisFile = `${modelFolder}/redis.js`; 12 | const indexFile = `${modelFolder}/index.js`; 13 | 14 | // if (logging) console.log('checking connection '); 15 | // if (logging) console.log('creating connection/mongo.js'); 16 | if (!fs.existsSync(redisFile)) { 17 | // if (logging) console.log('creating connection/mongo.js'); 18 | fs.mkdirSync(modelFolder); 19 | } 20 | 21 | // if (logging) console.log('creating connection/index.js'); 22 | fs.writeFileSync(indexFile, connectionIndex); 23 | 24 | // if (logging) console.log('creating mongoDB connection'); 25 | fs.writeFileSync(mongoFile, connectionMongo); 26 | 27 | // if (logging) console.log('creating redis connection'); 28 | fs.writeFileSync(redisFile, connectionRedis); 29 | 30 | // if (!fs.existsSync(redisFile)) { 31 | // 32 | // } 33 | } 34 | -------------------------------------------------------------------------------- /api/mongodb/makeModelIndex.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | const modelIndex = require('./modelIndex'); 4 | 5 | module.exports = ({ destination }) => { 6 | const modelFolder = `${destination}/models`; 7 | const indexFile = `${destination}/models/index.js`; 8 | // if (logging) console.log('updating models/index.js '); 9 | let items = fs.readdirSync(modelFolder); 10 | items = items.map((item) => item.replace('.js', '')); 11 | items = items.filter(item => item !== 'index'); 12 | const code = modelIndex(items); 13 | const pretty = beautify(code, { indent_size: 2, space_in_empty_paren: true }); 14 | fs.writeFileSync(indexFile, pretty); 15 | }; 16 | -------------------------------------------------------------------------------- /api/mongodb/makeSchema.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | 4 | const replaceStringValues = (str) => { 5 | str = str.replace(/"String"/g, "String"); 6 | str = str.replace(/"string"/g, "String"); 7 | str = str.replace(/"boolean"/g, "Boolean"); 8 | str = str.replace(/"Boolean"/g, "Boolean"); 9 | str = str.replace(/"Number"/g, "Number"); 10 | str = str.replace(/"number"/g, "Number"); 11 | str = str.replace(/"ObjectId"/g, "Schema.Types.ObjectId"); 12 | str = str.replace(/"default"/g, "default"); 13 | str = str.replace(/"type"/g, "type"); 14 | str = str.replace(/"htmlType"/g, "htmlType"); 15 | str = str.replace(/"unique"/g, "unique"); 16 | str = str.replace(/"trim"/g, "trim"); 17 | str = str.replace(/"required"/g, "required"); 18 | str = str.replace(/"immutable"/g, "immutable"); 19 | str = str.replace(/"enum"/g, "enum"); 20 | 21 | return str; 22 | }; 23 | 24 | const doMakeSchema = ({ schema, destination }) => { 25 | const { uppercase } = require('../utils'); 26 | const name = schema.name; 27 | const modelFolder = `${destination}/models`; 28 | const modelFile = `${modelFolder}/${uppercase(name)}.js`; 29 | // if (logging) console.log('making schema ', schema); 30 | 31 | if (!fs.existsSync(modelFolder)) { 32 | // if (logging) console.log('creating models/'); 33 | fs.mkdirSync(modelFolder); 34 | } 35 | 36 | const code = [ 37 | "const database = require('../connection/mongo');", 38 | "const { Schema } = require('mongoose');", 39 | "const mongoosePaginate = require('mongoose-paginate-v2');", 40 | "const schema = new Schema({", 41 | ]; 42 | 43 | // format the model 44 | Object.keys(schema.schema).forEach(key => { 45 | let value = schema.schema[key]; 46 | if (schema.schema[key].type) { 47 | value = JSON.stringify(schema.schema[key]); 48 | value = replaceStringValues(value); 49 | code.push(` ${key}: ${value},`); 50 | } else if (Array.isArray(schema.schema[key])) { 51 | code.push(` ${key}: [`); 52 | schema.schema[key].forEach((item) => { 53 | if (item.type) { 54 | value = JSON.stringify(item); 55 | value = replaceStringValues(value); 56 | code.push(`${value}`); 57 | } else { 58 | let _value = "{}"; 59 | Object.keys(item).forEach(_key => { 60 | code.push(`{ ${_key}: `); 61 | _value = JSON.stringify(item[_key]); 62 | _value = replaceStringValues(_value); 63 | code.push(`${_value}, }`); 64 | }); 65 | } 66 | }); 67 | code.push(` ],`); 68 | } else { 69 | code.push(` ${key}: {`); 70 | let _value = {}; 71 | Object.keys(schema.schema[key]).forEach(_key => { 72 | _value = JSON.stringify(schema.schema[key][_key]); 73 | _value = replaceStringValues(_value); 74 | code.push(` ${_key}: ${_value},`); 75 | }); 76 | code.push(`},`); 77 | } 78 | }); 79 | // add timestamps 80 | code.push(`}, 81 | { 82 | timestamps: true 83 | });`); 84 | // assign statics 85 | if (schema.statics) { 86 | const statics = []; 87 | Object.keys(schema.statics).forEach(key => { 88 | statics.push(`schema.statics.${key} = ${JSON.stringify(schema.statics[key])};`); 89 | }); 90 | statics.forEach((_static) => code.push(`${_static}`)); 91 | } 92 | 93 | // button up 94 | code.push(`schema.plugin(mongoosePaginate);`); 95 | code.push(`module.exports = database.model("${uppercase(name)}", schema);`); 96 | const pretty = beautify(code.join('\n'), { indent_size: 2, space_in_empty_paren: true }); 97 | fs.writeFileSync(modelFile, pretty); 98 | }; 99 | 100 | module.exports = ({ schema, destination}) => { 101 | schema = require(schema); // eslint-disable-line 102 | if (Array.isArray(schema)) { 103 | schema.forEach((_schema) => doMakeSchema({ schema: _schema, destination })); 104 | } else { 105 | doMakeSchema({ schema, destination }); 106 | } 107 | }; 108 | -------------------------------------------------------------------------------- /api/mongodb/modelIndex.js: -------------------------------------------------------------------------------- 1 | module.exports = (models = []) => { 2 | const code = ["module.exports = {"]; 3 | models.forEach((model) => code.push(`${model}: require('./${model}'),`)); 4 | code.push("};"); 5 | return code.join('\n'); 6 | }; 7 | -------------------------------------------------------------------------------- /api/redis/connectionRedis.js: -------------------------------------------------------------------------------- 1 | module.exports = ` 2 | const _redis = require("redis"); 3 | const { promisify } = require("util"); 4 | const { redisConfig } = require("../config"); 5 | const { host, port, password } = redisConfig; 6 | const redis = _redis.createClient({ host, port, password }); 7 | const getAsync = promisify(redis.get).bind(redis); 8 | redis.on('connect', () => { 9 | console.error("Redis OKAY"); 10 | }); 11 | redis.on("error", (err) => { 12 | console.error("Redis Connection Error", err); 13 | }); 14 | module.exports.redis = redis; 15 | module.exports.redisGetAsync = getAsync; 16 | 17 | ` 18 | -------------------------------------------------------------------------------- /api/redis/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | connectionRedis: require('./connectionRedis'), 3 | } 4 | -------------------------------------------------------------------------------- /api/router/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | makeRouter: require('./makeRouter'), 3 | routerCode: require('./routerCode'), 4 | }; 5 | -------------------------------------------------------------------------------- /api/router/indexCode.js: -------------------------------------------------------------------------------- 1 | const beautify = require('js-beautify').js; 2 | 3 | module.exports = ({ names }) => { 4 | const code = []; 5 | code.push(`module.exports = {`); 6 | names.forEach((name) => { 7 | code.push(`${name}: require('./${name}.js'),`); 8 | }); 9 | code.push(`};`); 10 | const pretty = beautify(code.join('\n'), { indent_size: 2, space_in_empty_paren: true }); 11 | return pretty; 12 | }; 13 | -------------------------------------------------------------------------------- /api/router/makeRouter.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | module.exports = ({ schema, destination, name }) => { 4 | const indexCode = require('./indexCode'); 5 | const routerCode = require('./routerCode'); 6 | 7 | const modelFolder = `${destination}/router`; 8 | const indexFile = `${modelFolder}/index.js`; 9 | 10 | schema = require(schema); // eslint-disable-line 11 | const isArray = Array.isArray(schema); 12 | 13 | 14 | if (!fs.existsSync(modelFolder)) { 15 | fs.mkdirSync(modelFolder); 16 | } 17 | 18 | if (isArray) { 19 | const names = []; 20 | schema.forEach((s) => { 21 | names.push(s.name); 22 | const routerFile = `${modelFolder}/${s.name}.js`; 23 | fs.writeFileSync(routerFile, routerCode({ name: s.name })); 24 | }); 25 | fs.writeFileSync(indexFile, indexCode({ names })); 26 | } else { 27 | const routerFile = `${modelFolder}/${schema.name}.js`; 28 | console.log('ROUTERFILE', routerFile) 29 | fs.writeFileSync(routerFile, routerCode({ name: schema.name })); 30 | fs.writeFileSync(indexFile, indexCode({ names: [schema.name] })); 31 | } 32 | // @TODO redis? 33 | // if (!fs.existsSync(redisFile)) { 34 | // if (logging) console.log('creating redis connection'); 35 | // fs.writeFileSync(redisFile, connectionRedis); 36 | // } 37 | }; 38 | -------------------------------------------------------------------------------- /api/router/routerCode.js: -------------------------------------------------------------------------------- 1 | const beautify = require('js-beautify').js; 2 | 3 | module.exports = ({ name }) => { 4 | const { uppercase } = require('../utils'); 5 | 6 | const code = ` 7 | const express = require('express'); 8 | const router = express.Router(); 9 | const { 10 | create${uppercase(name)}, 11 | delete${uppercase(name)}, 12 | get${uppercase(name)}, 13 | getOne${uppercase(name)}, 14 | update${uppercase(name)} 15 | } = require('../controller/${name}'); 16 | 17 | router.get('/${name}/:id', getOne${uppercase(name)}); 18 | router.get('/${name}s', get${uppercase(name)}); 19 | router.post('/${name}', create${uppercase(name)}); 20 | router.put('/${name}/:id', update${uppercase(name)}); 21 | router.delete('/${name}/:id', delete${uppercase(name)}); 22 | 23 | module.exports = router; 24 | 25 | `; 26 | const pretty = beautify(code, { indent_size: 2, space_in_empty_paren: true }); 27 | return pretty; 28 | }; 29 | -------------------------------------------------------------------------------- /api/server/dockerFile.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | 4 | module.exports = ({ destination, logging }) => { 5 | const modelFolder = `${destination}`; 6 | const dockerFile = `${modelFolder}/Dockerfile`; 7 | const dockerIgnore = `${modelFolder}/.dockerignore`; 8 | 9 | // if (logging) console.log('checking Dockerfile'); 10 | 11 | const code = ` 12 | FROM node:alpine 13 | RUN mkdir -p /var/app 14 | WORKDIR /var/app 15 | COPY . /var/app 16 | RUN npm install --production 17 | RUN npm run docs 18 | EXPOSE 7777 19 | CMD [ "npm", "start" ] 20 | `; 21 | fs.writeFileSync(dockerFile, code); 22 | fs.writeFileSync(dockerIgnore, ` 23 | node_modules 24 | `); 25 | }; 26 | -------------------------------------------------------------------------------- /api/server/eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": false, 4 | "es6": true, 5 | "mocha": true, 6 | "node": true, 7 | "worker": false, 8 | "jest": true 9 | }, 10 | "extends": ["airbnb"], 11 | "parserOptions": { 12 | "ecmaVersion": 2018, 13 | "sourceType": "module", 14 | "ecmaFeatures": { 15 | "globalReturn": true, 16 | "impliedStrict": true, 17 | "destructuring": true, 18 | "restParams": true, 19 | "spread": true 20 | } 21 | }, 22 | "rules": { 23 | "array-bracket-spacing": "warn", 24 | "array-callback-return": "warn", 25 | "arrow-body-style": "off", 26 | "arrow-parens": "off", 27 | "arrow-spacing": [ 28 | "warn", 29 | { 30 | "after": true, 31 | "before": true 32 | } 33 | ], 34 | "camelcase": "off", 35 | "class-methods-use-this": "warn", 36 | "comma-dangle": [ 37 | "warn", 38 | { 39 | "arrays": "only-multiline", 40 | "objects": "only-multiline", 41 | "imports": "only-multiline", 42 | "exports": "only-multiline", 43 | "functions": "ignore" 44 | } 45 | ], 46 | "consistent-return": "off", 47 | "function-paren-newline": "off", 48 | "global-require": "off", 49 | "guard-for-in": "off", 50 | "implicit-arrow-linebreak": "off", 51 | "import/extensions": "warn", 52 | "import/first": "off", 53 | "import/newline-after-import": "off", 54 | "import/no-extraneous-dependencies": "off", 55 | "import/no-named-as-default": "warn", 56 | "import/no-named-as-default-member": "warn", 57 | "import/no-useless-path-segments": "warn", 58 | "import/order": "off", 59 | "import/prefer-default-export": "off", 60 | "indent": "warn", 61 | "jsx-a11y/alt-text": "off", 62 | "jsx-a11y/anchor-is-valid": "off", 63 | "jsx-a11y/click-events-have-key-events": "off", 64 | "jsx-a11y/label-has-associated-control": "off", 65 | "jsx-a11y/label-has-for": "off", 66 | "jsx-a11y/media-has-caption": "off", 67 | "jsx-a11y/mouse-events-have-key-events": "off", 68 | "jsx-a11y/no-noninteractive-element-interactions": "off", 69 | "jsx-a11y/no-noninteractive-tabindex": "off", 70 | "jsx-a11y/no-static-element-interactions": "off", 71 | "lines-between-class-members": "off", 72 | "max-len": [ 73 | "off", 74 | { 75 | "code": 98, 76 | "comments": 120, 77 | "ignoreRegExpLiterals": true, 78 | "ignoreStrings": true, 79 | "ignoreTemplateLiterals": true, 80 | "ignoreUrls": true, 81 | "tabWidth": 2 82 | } 83 | ], 84 | "new-parens": "warn", 85 | "no-bitwise": "off", 86 | "no-case-declarations": "warn", 87 | "no-console": [ 88 | "warn", 89 | { 90 | "allow": ["error"] 91 | } 92 | ], 93 | "no-continue": "off", 94 | "no-else-return": "off", 95 | "no-empty": "warn", 96 | "no-extra-boolean-cast": "off", 97 | "no-lonely-if": "warn", 98 | "no-mixed-operators": "off", 99 | "no-mixed-spaces-and-tabs": "warn", 100 | "no-multi-assign": "off", 101 | "no-multiple-empty-lines": "warn", 102 | "no-multi-spaces": "warn", 103 | "no-param-reassign": "off", 104 | "no-plusplus": "off", 105 | "no-prototype-builtins": "off", 106 | "no-restricted-globals": "off", 107 | "no-restricted-properties": "off", 108 | "no-restricted-syntax": "warn", 109 | "no-return-assign": "off", 110 | "no-return-await": "warn", 111 | "no-shadow": "warn", 112 | "no-tabs": "warn", 113 | "no-trailing-spaces": "warn", 114 | "no-undef-init": "off", 115 | "no-underscore-dangle": "off", 116 | "no-unneeded-ternary": "off", 117 | "no-unreachable": "warn", 118 | "no-unused-vars": ["warn", { 119 | "ignoreRestSiblings": true 120 | }], 121 | "no-useless-escape": "warn", 122 | "no-useless-return": "warn", 123 | "no-warning-comments": "off", 124 | "object-curly-newline": [ 125 | "off", 126 | { 127 | "ObjectExpression": { "multiline": true, "minProperties": 3 }, 128 | "ObjectPattern": { "multiline": true, "minProperties": 3 }, 129 | "ImportDeclaration": { "multiline": true, "minProperties": 3 }, 130 | "ExportDeclaration": { "multiline": true, "minProperties": 3 } 131 | } 132 | ], 133 | "object-curly-spacing": "off", 134 | "object-property-newline": "off", 135 | "operator-assignment": "off", 136 | "operator-linebreak": "off", 137 | "padded-blocks": "off", 138 | "prefer-const": "warn", 139 | "prefer-destructuring": "off", 140 | "prefer-promise-reject-errors": "warn", 141 | "prefer-spread": "warn", 142 | "prefer-template": "warn", 143 | "quotes": "off", 144 | "semi": "warn", 145 | "space-before-function-paren": "off", 146 | "spaced-comment": "off", 147 | "space-in-parens": "warn" 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /api/server/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | serverIndex: require('./serverIndex'), 3 | userCan: require('./userCan'), 4 | dockerFile: require('./dockerFile'), 5 | writePackageJSON: require('./writePackageJSON'), 6 | writeEslint: require('./writeEslint'), 7 | writeDockerIgnore: require('./writeDockerIgnore'), 8 | readme: require('./readme'), 9 | writeGitIgnore: require('./writeGitIgnore'), 10 | }; 11 | -------------------------------------------------------------------------------- /api/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "PORT=${PORT} NODE_ENV=production npm run dev-action", 8 | "dev": "nodemon --exec npm run dev-action --ignore swagger.json --ignore swagger.yaml", 9 | "dev-action": "npm run docs && node index.js", 10 | "test": "mocha --timeout 10000 --exit", 11 | "docs": "swagger-inline './controller/*/*.js' './models/*.js' --base './swaggerBase.yaml' --out swagger.json" 12 | }, 13 | "author": "", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "chai": "^4.2.0", 17 | "chai-http": "^4.3.0", 18 | "eslint": "^5.8.0", 19 | "eslint-config-airbnb": "^17.1.0", 20 | "eslint-plugin-import": "^2.14.0", 21 | "eslint-plugin-jsx-a11y": "^6.2.1", 22 | "eslint-plugin-react": "^7.13.0", 23 | "mocha": "^6.1.4", 24 | "mocha-http": "^0.2.5", 25 | "prettier": "^1.14.3", 26 | "prettier-eslint": "^8.8.2", 27 | "prettier-eslint-cli": "^4.7.1", 28 | "prettier-stylelint": "^0.4.2" 29 | }, 30 | "dependencies": { 31 | "body-parser": "^1.19.0", 32 | "cors": "^2.8.5", 33 | "express": "^4.17.1", 34 | "mongoose": "^5.5.12", 35 | "express-graphql": "^0.8.0", 36 | "graphql": "^14.3.1", 37 | "graphql-compose": "^7.1.0", 38 | "graphql-compose-connection": "^6.0.3", 39 | "graphql-compose-mongoose": "^7.0.3", 40 | "graphql-compose-pagination": "^6.0.3", 41 | "graphql-server-express": "^1.4.0", 42 | "mongoose-paginate-v2": "^1.3.0", 43 | "swagger-inline": "^1.0.5", 44 | "swagger-ui-dist": "^3.22.2" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /api/server/readme.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | module.exports = ({ schema, destination }) => { 4 | const { uppercase } = require('../utils'); 5 | const modelFolder = `${destination}`; 6 | const indexFile = `${modelFolder}/README.md`; 7 | schema = require(schema); // eslint-disable-line 8 | 9 | const isArray = Array.isArray(schema); 10 | 11 | const names = []; 12 | if (isArray) { 13 | schema.forEach((s) => names.push(uppercase(s.name))); 14 | } else { 15 | names.push(schema.name); 16 | } 17 | const code = ` 18 | # Generated API for ${names.join(', ')} 19 | 20 | - auto generated OAS 3.0.0 compliant api 21 | 22 | ## Run 23 | 24 | \`\`\`sh 25 | npm start 26 | \`\`\` 27 | 28 | ## Build 29 | 30 | \`\`\`sh 31 | docker build -t container-name:0.0.1 . 32 | \`\`\` 33 | 34 | ## Documentation 35 | 36 | - Documenation is auto generated and is available via swagger at [http://localhost:3000](http://localhost:3000) 37 | 38 | \`\`\`sh 39 | npm run docs 40 | \`\`\` 41 | 42 | ## Configs 43 | 44 | configs are located at **configs/configs.json** 45 | 46 | \`\`\`json 47 | { 48 | "port": 7777, 49 | "db": { 50 | "mongoURL": "mongodb://localhost:27017/sugar", 51 | "mongoOptions": { 52 | "useNewUrlParser": true 53 | } 54 | }, 55 | "env": "local", 56 | "userCanApiKey": "123456" 57 | } 58 | \`\`\` 59 | 60 | 61 | `; 62 | 63 | // to be added to configs json 64 | // "jwt_secret": "e4b717984dab52c8168f5b61bbb8bea42acfe921", 65 | // "basicAuth": { 66 | // "username": "sugar", 67 | // "password": "kubes" 68 | // }, 69 | // "redisConfig": { 70 | // "host": "localhost", 71 | // "port": 6379, 72 | // "password": "" 73 | // }, 74 | // 75 | fs.writeFileSync(indexFile, code); 76 | }; 77 | -------------------------------------------------------------------------------- /api/server/serverIndex.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | const { uppercase } = require('../utils'); 4 | const { tcString } = require('../graphql'); 5 | 6 | module.exports = ({ schema, destination, logging, flavor }) => { 7 | schema = require(schema); // eslint-disable-line 8 | 9 | const isArray = Array.isArray(schema); 10 | 11 | const names = []; 12 | if (isArray) { 13 | schema.forEach((s) => names.push(uppercase(s.name))); 14 | } else { 15 | names.push(uppercase(schema.name)); 16 | } 17 | const modelFolder = `${destination}`; 18 | const indexFile = `${modelFolder}/index.js`; 19 | // if (logging) console.log('checking server index '); 20 | const code = []; 21 | 22 | const graphql = [ 23 | ` 24 | // graphql 25 | const graphqlHTTP = require('express-graphql'); 26 | const { composeWithMongoose } = require('graphql-compose-mongoose/node8'); 27 | const { schemaComposer } = require('graphql-compose'); 28 | const customizationOptions = {}; // left it empty for simplicity, described below 29 | const { ${names.join(', ')} } = require('./models'); 30 | ` 31 | ]; 32 | 33 | 34 | names.forEach((name) => { 35 | graphql.push(`const ${uppercase(name)}TC = composeWithMongoose(${uppercase(name)}, customizationOptions);`); 36 | }); 37 | 38 | graphql.push('schemaComposer.Query.addFields({'); 39 | names.forEach((name) => { 40 | name = name.toLowerCase(); 41 | graphql.push(`${name}ById: ${uppercase(name)}TC.getResolver('findById'),`); 42 | graphql.push(`${name}ByIds: ${uppercase(name)}TC.getResolver('findByIds'),`); 43 | graphql.push(`${name}One: ${uppercase(name)}TC.getResolver('findOne'),`); 44 | graphql.push(`${name}Many: ${uppercase(name)}TC.getResolver('findMany'),`); 45 | graphql.push(`${name}Count: ${uppercase(name)}TC.getResolver('count'),`); 46 | graphql.push(`${name}Connection: ${uppercase(name)}TC.getResolver('connection'),`); 47 | graphql.push(`${name}Pagination: ${uppercase(name)}TC.getResolver('pagination'),`); 48 | }); 49 | graphql.push(' });'); 50 | 51 | graphql.push('schemaComposer.Mutation.addFields({'); 52 | names.forEach((name) => { 53 | name = name.toLowerCase(); 54 | graphql.push(`${name}CreateOne: ${uppercase(name)}TC.getResolver('createOne'),`); 55 | graphql.push(`${name}CreateMany: ${uppercase(name)}TC.getResolver('createMany'),`); 56 | graphql.push(`${name}UpdateById: ${uppercase(name)}TC.getResolver('updateById'),`); 57 | graphql.push(`${name}UpdateOne: ${uppercase(name)}TC.getResolver('updateOne'),`); 58 | graphql.push(`${name}UpdateMany: ${uppercase(name)}TC.getResolver('updateMany'),`); 59 | graphql.push(`${name}RemoveById: ${uppercase(name)}TC.getResolver('removeById'),`); 60 | graphql.push(`${name}RemoveOne: ${uppercase(name)}TC.getResolver('removeOne'),`); 61 | graphql.push(`${name}RemoveMany: ${uppercase(name)}TC.getResolver('removeMany'),`); 62 | }); 63 | graphql.push('});'); 64 | graphql.push('const graphqlSchema = schemaComposer.buildSchema();'); 65 | graphql.push(` 66 | app.use('/graphql', graphqlHTTP({ 67 | schema: graphqlSchema, 68 | graphiql: true 69 | })); 70 | `); 71 | 72 | code.push(` 73 | const express = require('express'); 74 | const fs = require('fs'); 75 | const bodyParser = require('body-parser'); 76 | const cors = require('cors'); 77 | const swag = require('swagger-ui-dist'); 78 | const db = require('./connection/mongo'); // eslint-disable-line 79 | let { port } = require('./configs/config'); 80 | if (process.env.PORT) port = process.env.PORT; 81 | const abs = swag.getAbsoluteFSPath(); 82 | const app = express(); 83 | const jsonParser = bodyParser.json(); 84 | app.use(cors()); 85 | app.use(jsonParser); 86 | ${flavor !== 'graphql' ? '' : graphql.join('')} 87 | app.use((req, res, next) => { 88 | console.log(\`\${req.method} \${req.url}\`); 89 | return next(); 90 | }); 91 | app.use('/health', (req, res) => res.status(200).json({ healthy: true, time: new Date().getTime() })); 92 | 93 | // routes 94 | `); 95 | names.forEach((name) => { 96 | name = name.toLowerCase(); 97 | code.push(`app.use([ require('./router/${name}') ]);`); 98 | }); 99 | code.push('const indexContent = fs.readFileSync(`${abs}/index.html`).toString().replace("https://petstore.swagger.io/v2/swagger.json", `http://localhost:${port}/swagger.json`);'); // eslint-disable-line 100 | code.push("app.use('/swagger.json', express.static('./swagger.json'));"); 101 | code.push('app.get("/", (req, res) => res.send(indexContent));'); 102 | code.push('app.get("/index.html", (req, res) => res.send(indexContent));'); 103 | code.push("app.use(express.static(abs));"); 104 | code.push("app.listen(port, () => console.log(\`SugarKubes API: \${port}!\`)); // eslint-disable-line"); // eslint-disable-line 105 | code.push("module.exports = app; // for testing"); 106 | // if (logging) console.log('creating server entrypoint'); 107 | const pretty = beautify(code.join('\n'), { indent_size: 2, space_in_empty_paren: true }); 108 | 109 | fs.writeFileSync(indexFile, pretty); 110 | }; 111 | -------------------------------------------------------------------------------- /api/server/userCan.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | 4 | module.exports = ({ destination, logging, }) => { 5 | const modelFolder = `${destination}/user-can`; 6 | const indexFile = `${modelFolder}/index.js`; 7 | 8 | // if (logging) console.log('checking user-can'); 9 | if (!fs.existsSync(modelFolder)) { 10 | // if (logging) console.log('creating user-can'); 11 | fs.mkdirSync(modelFolder); 12 | } 13 | 14 | const code = []; 15 | code.push(` 16 | module.exports = (apiKey) => (params) => { 17 | // console.log('user can ', params); 18 | 19 | // @TODO make api call to userCan.sugarkubes.io and of course build the service. 20 | 21 | return true; 22 | }; 23 | `); 24 | // if (logging) console.log('creating server entrypoint'); 25 | const pretty = beautify(code.join('\n'), { indent_size: 2, space_in_empty_paren: true }); 26 | 27 | fs.writeFileSync(indexFile, pretty); 28 | } 29 | -------------------------------------------------------------------------------- /api/server/writeDockerIgnore.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | 4 | module.exports = ({ destination, logging }) => { 5 | const modelFolder = `${destination}`; 6 | const indexFile = `${modelFolder}/.dockerignore`; 7 | // if (logging) console.log('creating eslint'); 8 | const code = ['node_modules']; 9 | const pretty = beautify(code.join('\n'), { indent_size: 2, space_in_empty_paren: true }); 10 | fs.writeFileSync(indexFile, pretty); 11 | }; 12 | -------------------------------------------------------------------------------- /api/server/writeEslint.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | 4 | module.exports = ({ destination, logging, }) => { 5 | const modelFolder = `${destination}`; 6 | const indexFile = `${modelFolder}/.eslintrc`; 7 | const code = require('./eslint.json') 8 | // if (logging) console.log('creating eslint'); 9 | fs.writeFileSync(indexFile, JSON.stringify(code, null, 2)); 10 | }; 11 | -------------------------------------------------------------------------------- /api/server/writeGitIgnore.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | 4 | module.exports = ({ destination, logging }) => { 5 | const modelFolder = `${destination}`; 6 | const indexFile = `${modelFolder}/.gitignore`; 7 | // if (logging) console.log('creating eslint'); 8 | const code = ['node_modules']; 9 | const pretty = beautify(code.join('\n'), { indent_size: 2, space_in_empty_paren: true }); 10 | fs.writeFileSync(indexFile, pretty); 11 | }; 12 | -------------------------------------------------------------------------------- /api/server/writePackageJSON.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | // const beautify = require('js-beautify').js; 3 | 4 | module.exports = ({ destination, flavor }) => { 5 | const modelFolder = `${destination}`; 6 | const indexFile = `${modelFolder}/package.json`; 7 | const code = require('./package.json'); 8 | if (flavor !== 'graphql') { 9 | delete code.dependencies.graphql; 10 | delete code.dependencies["graphql-server-express"]; 11 | delete code.dependencies["express-graphql"]; 12 | } 13 | // if (logging) console.log('checking server index '); 14 | // if (logging) console.log('creating server entrypoint'); 15 | fs.writeFileSync(indexFile, JSON.stringify(code, null, 2)); 16 | }; 17 | -------------------------------------------------------------------------------- /api/swagger/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | safeType: require('./safeType'), 3 | safeDefault: require('./safeDefault'), 4 | schemaDefinition: require('./schemaDefinition'), 5 | makeProperties: require('./makeProperties'), 6 | makeSwaggerModelDefinitions: require('./makeSwaggerModelDefinitions'), 7 | 8 | }; 9 | -------------------------------------------------------------------------------- /api/swagger/makeProperties.js: -------------------------------------------------------------------------------- 1 | module.exports = (params, space = '') => { 2 | const safeType = require('./safeType'); 3 | const safeDefault = require('./safeDefault'); 4 | const code = []; 5 | Object.keys(params).forEach(key => { 6 | code.push(`${space}${key}:`); 7 | code.push(`${space} type: ${safeType(params[key].type)}`); 8 | if (typeof params[key].default !== 'undefined') code.push(`${space} default: ${safeDefault(params[key].default)}`); 9 | if (typeof params[key].example !== 'undefined') code.push(`${space} example: ${JSON.stringify(params[key].example)}`); 10 | 11 | }); 12 | return code; 13 | }; 14 | -------------------------------------------------------------------------------- /api/swagger/makeSwaggerModelDefinitions.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const schemaDefinition = require('./schemaDefinition'); 3 | 4 | module.exports = ({ schema, logging, destination }) => { 5 | schema = require(schema); // eslint-disable-line 6 | const uppercase = require('../utils/uppercase'); 7 | const swaggerFile = `${destination}/swagger.yaml`; 8 | let names = ""; 9 | const isArray = Array.isArray(schema); 10 | 11 | if (isArray) { 12 | schema.forEach((s, i) => names += `${uppercase(s.name)}${i < schema.length - 1 ? ', ' : '.'}`); 13 | } else { 14 | names = uppercase(schema.name); 15 | } 16 | 17 | const top = ` 18 | openapi: 3.0.0 19 | info: 20 | title: ${names} API 21 | description: Generated API for ${names} 22 | version: 0.0.1 23 | servers: 24 | - url: http://localhost:7777 25 | description: local7777 26 | - url: https://sugar-generate-demo-iqwznvcybq-uc.a.run.app 27 | description: test 28 | - url: http://localhost:3000 29 | description: local3000 30 | components: 31 | schemas: 32 | `; 33 | const code = [top]; 34 | 35 | if (isArray) { 36 | schema.forEach((s) => code.push(schemaDefinition({ schema: s, name: s.name }))); 37 | } else { 38 | code.push(schemaDefinition({ schema, name: schema.name })); 39 | } 40 | // if (logging) console.log('make swagger model definitions '); 41 | fs.writeFileSync(swaggerFile, code.join('\n')); 42 | }; 43 | -------------------------------------------------------------------------------- /api/swagger/safeDefault.js: -------------------------------------------------------------------------------- 1 | module.exports = (defaultString) => { 2 | if (defaultString === "") return '""'; 3 | if (JSON.stringify(defaultString) === "{}") return `{}`; 4 | return defaultString; 5 | }; 6 | -------------------------------------------------------------------------------- /api/swagger/safeType.js: -------------------------------------------------------------------------------- 1 | module.exports = (str) => { 2 | if (str === 'objectid' || str === 'ObjectId') return 'string'; 3 | if (typeof str === 'undefined') return 'object'; 4 | return str; 5 | }; 6 | -------------------------------------------------------------------------------- /api/swagger/schemaDefinition.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ schema, name }) => { 2 | const safeType = require('./safeType'); 3 | const makeProperties = require('./makeProperties'); 4 | const safeDefault = require('./safeDefault'); 5 | const { uppercase, extraParams } = require('../utils'); 6 | let code = [ 7 | ` ${uppercase(name)}:`, 8 | ` properties:`, 9 | ]; 10 | const required = []; 11 | Object.keys(schema.schema).forEach(key => { 12 | const value = schema.schema[key]; 13 | if (value.type) { 14 | code.push(` ${key}:`); 15 | code.push(` type: ${safeType(value.type.toLowerCase())}`); 16 | if (typeof value.default !== 'undefined') code.push(` default: ${safeDefault(value.default)}`); 17 | if (typeof value.unique !== 'undefined') code.push(` unique: ${value.unique}`); 18 | 19 | if (value.required) required.push(key); 20 | } else if (Array.isArray(value)) { 21 | code.push(` ${key}:`); 22 | code.push(` type: array`); 23 | code.push(` items:`); 24 | value.forEach((val) => { 25 | if (val.type) { 26 | code.push(` type: ${safeType(val.type.toLowerCase())}`); 27 | } else { 28 | Object.keys(val).forEach(_key => { 29 | const _value = val[_key]; 30 | code.push(` ${_key}:`); 31 | code.push(` type: ${safeType(_value.type.toLowerCase())}`); 32 | if (typeof _value.default !== 'undefined') code.push(` default: ${safeDefault(_value.default)}`); 33 | }); 34 | } 35 | }); 36 | } else { 37 | code.push(` ${key}:`); 38 | code.push(` type: object`); 39 | code.push(` properties:`); 40 | Object.keys(value).forEach(_key => { 41 | const _value = value[_key]; 42 | code.push(` ${_key}:`); 43 | code.push(` type: ${safeType(_value.type.toLowerCase())}`); 44 | if (typeof _value.default !== 'undefined') code.push(` default: ${safeDefault(_value.default)}`); 45 | }); 46 | } 47 | }); 48 | if (required.length > 0) code.push(' required:'); 49 | required.forEach(item => code.push(` - ${item}`)); 50 | 51 | // Extended one to include get parameters 52 | // ExtendedErrorModel: 53 | // allOf: # Combines the BasicErrorModel and the inline model 54 | // - $ref: '#/components/schemas/BasicErrorModel' 55 | // - type: object 56 | // required: 57 | // - rootCause 58 | // properties: 59 | // rootCause: 60 | // type: string 61 | code.push(` Extended${uppercase(name)}:`); 62 | code.push(` allOf:`); 63 | code.push(` - $ref: '#/components/schemas/${uppercase(name)}'`); 64 | code.push(` - type: object`); 65 | code.push(` properties:`); 66 | code = code.concat(makeProperties(extraParams, ' ')); 67 | return code.join('\n'); 68 | }; 69 | -------------------------------------------------------------------------------- /api/tests/fakeObject.js: -------------------------------------------------------------------------------- 1 | const fo = (schema, asString = false, parseSchema = true) => { 2 | const genData = require('./genData'); 3 | 4 | // if (parseSchema) schema = require(schema).schema; // eslint-disable-line 5 | const fakeObject = {}; 6 | Object.keys(schema).forEach((key) => { 7 | fakeObject[key] = genData(schema[key].type, schema[key], schema[key].enum); 8 | }); 9 | 10 | if (!asString) return fakeObject; 11 | 12 | // into string 13 | const str = []; 14 | Object.keys(fakeObject).forEach(key => { 15 | 16 | const value = schema[key].type === 'String' ? `"${fakeObject[key]}"` : fakeObject[key]; 17 | const isObject = typeof value === 'object'; 18 | let abstractedValue = value; 19 | if (isObject) { 20 | if (asString) { 21 | const _str = []; 22 | Object.keys(value).forEach(_key => { 23 | _str.push(`${_key}: ${value[_key]}`); 24 | }); 25 | abstractedValue = `" ${_str.join(', ')} "`; 26 | } 27 | } 28 | str.push(`${key}: ${abstractedValue}`); 29 | }); 30 | return { obj: fakeObject, str: str.join(', ') }; 31 | }; 32 | module.exports = fo; 33 | -------------------------------------------------------------------------------- /api/tests/genData.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = (type, value, isEnum = false) => { 3 | const subDocHelper = require('./subDocHelper'); 4 | 5 | if (isEnum) { 6 | return isEnum[0]; 7 | } 8 | if (!type) { 9 | return subDocHelper(value); 10 | } 11 | switch (type.toLowerCase()) { 12 | case 'string': 13 | return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); 14 | case 'number': 15 | return Math.floor(Math.random() * 1000); 16 | case 'object': 17 | return { hi: true, cool: "okay", bean: 123 }; 18 | case 'boolean': 19 | return Math.floor(Math.random() * 1000) > 500 ? true : false; 20 | case 'enum': 21 | default: 22 | 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /api/tests/graphqlSafe.js: -------------------------------------------------------------------------------- 1 | module.exports = (prop) => { 2 | switch (typeof prop) { 3 | case "string": 4 | return `"${prop}"`; 5 | default: 6 | return `${prop}`; 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /api/tests/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | makeTests: require('./makeTests'), 3 | fakeObject: require('./fakeObject'), 4 | }; 5 | -------------------------------------------------------------------------------- /api/tests/makeGraphqlTests.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | const { uppercase } = require('../utils'); 4 | 5 | module.exports = ({ originalSchema, schema, destination, logging, }) => { 6 | const fakeObject = require('./fakeObject'); 7 | const graphqlSafe = require('./graphqlSafe'); 8 | const randomPropertyChange = require('./randomPropertyChange'); 9 | const { getGraphqlProperties } = require('../graphql'); 10 | 11 | const name = schema.name; 12 | schema = schema.schema; 13 | const modelFolder = `${destination}/test`; 14 | const testFile = `${modelFolder}/${name}.js`; 15 | 16 | const fake1 = fakeObject(schema); 17 | const fake2 = fakeObject(schema); 18 | const fake3 = fakeObject(schema); 19 | const { fake3A, changedKey } = randomPropertyChange(schema, fake3); 20 | const fake4 = fakeObject(schema); 21 | const fake5 = fakeObject(schema); 22 | const fake6 = fakeObject(schema); 23 | 24 | 25 | const g1 = fakeObject(schema, true); 26 | const g1fakeObject = g1.obj; 27 | const g1fakeString = g1.str; 28 | 29 | const g2 = fakeObject(schema, true); 30 | const g2fakeObject = g2.obj; 31 | const g2fakeString = g2.str; 32 | 33 | const g3 = fakeObject(schema, true); 34 | const g3fakeObject = g3.obj; 35 | const g3fakeString = g3.str; 36 | 37 | const gchanged = randomPropertyChange(schema, g3fakeObject); 38 | 39 | const g3AfakeObject = gchanged.fake3A; 40 | const gchangedKey = gchanged.changedKey; 41 | 42 | const s = schema; 43 | const jsonSchema = JSON.stringify(s); 44 | 45 | const code = ` 46 | //During the test the env variable is set to test 47 | process.env.NODE_ENV = 'test'; 48 | const mongoose = require("mongoose"); 49 | const chai = require('chai'); 50 | const chaiHttp = require('chai-http'); 51 | 52 | const server = require('../index'); 53 | const { ${name} } = require('../models'); 54 | const should = chai.should(); 55 | chai.use(chaiHttp); 56 | 57 | 58 | const genData = (type, value, isEnum = false) => { 59 | if (isEnum) return value[0]; 60 | if (!type) { 61 | return subDocHelper(value); 62 | } 63 | switch (type.toLowerCase()) { 64 | case 'string': 65 | return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); 66 | case 'number': 67 | return Math.floor(Math.random() * 1000); 68 | case 'object': 69 | return { hi: true, cool: "okay", bean: 123 }; 70 | case 'boolean': 71 | return Math.floor(Math.random() * 1000) > 500 ? true : false; 72 | case 'enum': 73 | default: 74 | 75 | } 76 | }; 77 | 78 | const subDocHelper = (obj) => { 79 | if (!obj) throw new Error('subdocument is undefined'); 80 | const fake = {}; 81 | Object.keys(obj).forEach(key => { 82 | fake[key] = genData(obj[key].type); 83 | }); 84 | return fake; 85 | }; 86 | 87 | const fakeObject = (schema, asString = false) => { 88 | const fake = {}; 89 | Object.keys(schema).forEach((key) => { 90 | fake[key] = genData(schema[key].type, schema[key], schema[key].enum); 91 | }); 92 | 93 | if (!asString) return fake; 94 | 95 | // into string 96 | const str = []; 97 | Object.keys(fake).forEach(key => { 98 | const value = schema[key].type === 'String' ? \`"\${fake[key]}"\` : fake[key]; 99 | str.push(\`\${key}: \${value}\`); 100 | }); 101 | // console.log('fake ', fake); 102 | return { obj: fake, str: str.join(', ') }; 103 | }; 104 | 105 | 106 | describe('GRAPHQL: ${uppercase(name)}', () => { 107 | // beforeEach((done) => { 108 | // ${name}.deleteMany({}, () => {}) 109 | // setTimeout(() => { 110 | // [1,2,3,4,5].forEach(() => { 111 | // const obj = fakeObject(${jsonSchema}); 112 | // ${name}.create(obj) 113 | // }); 114 | // done() 115 | // }); 116 | // }); 117 | 118 | 119 | // describe('query ${name}s', () => { 120 | // it('it should get all the ${name}s', (done) => { 121 | // 122 | // chai.request(server) 123 | // .post('/graphql') 124 | // .send({ query:\` 125 | // query { 126 | // ${name}s { 127 | // _id 128 | // ${changedKey} 129 | // } 130 | // } 131 | // \` }) 132 | // .end((err, res) => { 133 | // const data = res.body.data; 134 | // res.should.have.status(200); 135 | // data.should.have.property('${name}s'); 136 | // data.${name}s.length.should.be.above(3); 137 | // data.${name}s[0].should.have.property('${changedKey}'); 138 | // data.${name}s[0].should.have.property('_id'); 139 | // done(); 140 | // }); 141 | // }); 142 | // }); 143 | 144 | // describe('mutation create${uppercase(name)}', () => { 145 | // it('it should create a ${name}', (done) => { 146 | // chai.request(server) 147 | // .post('/graphql') 148 | // .send({ query: \` 149 | // mutation { 150 | // create${uppercase(name)}(${g1fakeString}) { 151 | // _id 152 | /* ${getGraphqlProperties(schema, 'column')}*/ 153 | // } 154 | // } 155 | // \` }) 156 | // .end((err, res) => { 157 | // const data = res.body.data; 158 | // console.log('RES.BODY', res.body) 159 | // res.should.have.status(200); 160 | // data.should.have.property('create${uppercase(name)}'); 161 | // data.create${uppercase(name)}.${gchangedKey}.should.eql('${g1fakeObject[gchangedKey]}'); 162 | // done(); 163 | // }); 164 | // }); 165 | // }); 166 | // 167 | // 168 | // describe('query get one ${uppercase(name)}', () => { 169 | // it('it should create then get the created ${name}', (done) => { 170 | // chai.request(server) 171 | // .post('/graphql') 172 | // .send({ query: \` 173 | // mutation { 174 | // create${uppercase(name)}(${g2fakeString}) { 175 | // _id 176 | // } 177 | // } 178 | // \` }) 179 | // .end((err, res) => { 180 | // 181 | // const data = res.body.data; 182 | // res.should.have.status(200); 183 | // data.should.have.property('create${uppercase(name)}'); 184 | // data.create${uppercase(name)}.should.have.property('_id'); 185 | // const createdID = data.create${uppercase(name)}._id; 186 | // chai.request(server) 187 | // .post('/graphql') 188 | // .send({ query: \` 189 | // query { 190 | // ${name}(_id: "\${createdID}") { 191 | // _id 192 | // ${changedKey} 193 | // } 194 | // } 195 | // \`}) 196 | // .end((err, res) => { 197 | // const data = res.body.data; 198 | // res.should.have.status(200); 199 | // data.should.have.property('${name}'); 200 | // data.${name}.should.have.property('${changedKey}'); 201 | // data.${name}.should.have.property('_id'); 202 | // data.${name}._id.should.eql(createdID); 203 | // done(); 204 | // }); 205 | // 206 | // }); 207 | // }); 208 | // }); 209 | // 210 | // 211 | // describe('mutate ${uppercase(name)}', () => { 212 | // it('it should create then mutate ${name}', (done) => { 213 | // 214 | // chai.request(server) 215 | // .post('/graphql') 216 | // .send({ query: \` 217 | // mutation { 218 | // create${uppercase(name)}(${g3fakeString}) { 219 | // _id 220 | // } 221 | // } 222 | // \` }) 223 | // .end((err, res) => { 224 | // const data = res.body.data; 225 | // res.should.have.status(200); 226 | // data.should.have.property('create${uppercase(name)}'); 227 | // data.create${uppercase(name)}.should.have.property('_id'); 228 | // const createdID = data.create${uppercase(name)}._id; 229 | // 230 | // chai.request(server) 231 | // .post('/graphql') 232 | // .send({ query: \` 233 | // mutation { 234 | // update${uppercase(name)}(_id: "\${createdID}", ${gchangedKey}: ${graphqlSafe(g3AfakeObject[gchangedKey])}) { 235 | // _id 236 | // ${gchangedKey} 237 | // } 238 | // } 239 | // \`}) 240 | // .end((err, res) => { 241 | // const data = res.body.data; 242 | // res.should.have.status(200); 243 | // data.should.have.property('update${uppercase(name)}'); 244 | // data.update${uppercase(name)}.should.have.property('${gchangedKey}'); 245 | // data.update${uppercase(name)}.should.have.property('_id'); 246 | // // const changedString = String(data.update${uppercase(name)}.${gchangedKey}) 247 | // // changedString.should.eql("${fake3A[changedKey]}"); 248 | // done(); 249 | // }); 250 | // 251 | // }); 252 | // }); 253 | // }); 254 | 255 | }); 256 | `; 257 | const pretty = beautify(code, { indent_size: 2, space_in_empty_paren: true }); 258 | fs.writeFileSync(testFile, pretty); 259 | } 260 | -------------------------------------------------------------------------------- /api/tests/makeRestTests.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | const { uppercase } = require('../utils'); 4 | 5 | module.exports = ({ originalSchema, schema, destination, logging, }) => { 6 | const fakeObject = require('./fakeObject'); 7 | const randomPropertyChange = require('./randomPropertyChange'); 8 | 9 | const name = schema.name; 10 | schema = schema.schema; 11 | const modelFolder = `${destination}/test`; 12 | const testFile = `${modelFolder}/${name}-rest.js`; 13 | 14 | const fake1 = fakeObject(schema); 15 | const fake2 = fakeObject(schema); 16 | const fake3 = fakeObject(schema); 17 | const { fake3A, changedKey } = randomPropertyChange(schema, fake3); 18 | const fake4 = fakeObject(schema); 19 | const fake5 = fakeObject(schema); 20 | const fake6 = fakeObject(schema); 21 | 22 | const s = schema; 23 | const jsonSchema = JSON.stringify(s); 24 | 25 | const code = ` 26 | //During the test the env variable is set to test 27 | process.env.NODE_ENV = 'test'; 28 | const mongoose = require("mongoose"); 29 | const chai = require('chai'); 30 | const chaiHttp = require('chai-http'); 31 | 32 | const server = require('../index'); 33 | const ${uppercase(name)} = require('../models/${uppercase(name)}'); 34 | const should = chai.should(); 35 | chai.use(chaiHttp); 36 | 37 | 38 | const genData = (type, value, isEnum = false) => { 39 | if (isEnum) return isEnum[0]; 40 | if (!type) { 41 | return subDocHelper(value); 42 | } 43 | switch (type.toLowerCase()) { 44 | case 'string': 45 | return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); 46 | case 'number': 47 | return Math.floor(Math.random() * 1000); 48 | case 'object': 49 | return { hi: true, cool: "okay", bean: 123 }; 50 | case 'boolean': 51 | return Math.floor(Math.random() * 1000) > 500 ? true : false; 52 | case 'enum': 53 | default: 54 | 55 | } 56 | }; 57 | 58 | const subDocHelper = (obj) => { 59 | if (!obj) throw new Error('subdocument is undefined'); 60 | let fake = {}; 61 | if (Array.isArray(obj)) { 62 | fake = []; 63 | obj.forEach((o) => { 64 | if (o.type) { 65 | fake.push(genData(o.type)); 66 | } else { 67 | Object.keys(o).forEach(key => { 68 | fake.push({ [key]: genData(o[key].type) }); 69 | }); 70 | } 71 | }); 72 | } else { 73 | Object.keys(obj).forEach(key => { 74 | fake[key] = genData(obj[key].type); 75 | }); 76 | } 77 | return fake; 78 | }; 79 | 80 | const fakeObject = (schema, asString = false) => { 81 | const fakeObject = {}; 82 | Object.keys(schema).forEach((key) => { 83 | fakeObject[key] = genData(schema[key].type, schema[key], schema[key].enum); 84 | }); 85 | 86 | if (!asString) return fakeObject; 87 | 88 | // into string 89 | const str = []; 90 | Object.keys(fakeObject).forEach(key => { 91 | 92 | const value = schema[key].type === 'String' ? \`"\${fakeObject[key]}"\` : fakeObject[key]; 93 | const isObject = typeof value === 'object'; 94 | let abstractedValue = value; 95 | if (isObject) { 96 | if (asString) { 97 | const _str = []; 98 | Object.keys(value).forEach(_key => { 99 | _str.push(\`\${_key}: \${value[_key]}\`); 100 | }); 101 | abstractedValue = \`" \${_str.join(', ')} "\`; 102 | } 103 | } 104 | str.push(\`\${key}: \${abstractedValue}\`); 105 | }); 106 | }; 107 | 108 | describe('REST: ${uppercase(name)}', () => { 109 | before((done) => { 110 | ${uppercase(name)}.deleteMany({}, () => {}) 111 | setTimeout(() => { 112 | [1,2,3,4,5].forEach(() => { 113 | const obj = fakeObject(${jsonSchema}); 114 | ${uppercase(name)}.create(obj) 115 | }); 116 | done() 117 | }, 1000) 118 | }); 119 | /* 120 | * Test the /GET route 121 | */ 122 | describe('/GET ${uppercase(name)}', () => { 123 | it('it should GET all the ${uppercase(name)}s', (done) => { 124 | chai.request(server) 125 | .get('/${name}s') 126 | .end((err, res) => { 127 | res.should.have.status(200); 128 | res.body.should.have.property('${name}s'); 129 | res.body.${name}s.docs.should.have.lengthOf(5); 130 | res.body.${name}s.should.have.property('totalDocs'); 131 | res.body.${name}s.should.have.property('limit'); 132 | res.body.${name}s.should.have.property('offset'); 133 | done(); 134 | }); 135 | }); 136 | }); 137 | 138 | /* 139 | * Test the /POST route 140 | */ 141 | describe('/POST ${uppercase(name)}', () => { 142 | it('it should POST a ${uppercase(name)}', (done) => { 143 | const _${name} = ${JSON.stringify(fake1)}; 144 | chai.request(server) 145 | .post('/${name}') 146 | .send(_${name}) 147 | .end((err, res) => { 148 | res.should.have.status(200); 149 | res.body.should.be.a('object'); 150 | res.body.should.have.property('${name}'); 151 | res.body.${name}.should.include.any.keys("${Object.keys(fake1).join('","')}"); 152 | done(); 153 | }); 154 | }); 155 | }); 156 | 157 | /* 158 | * Test the /GET/:id route 159 | */ 160 | describe('/GET/:id ${name}', () => { 161 | it('it should GET a ${name} by the given id', (done) => { 162 | let _${name} = new ${uppercase(name)}(${JSON.stringify(fake2)}); 163 | _${name}.save((err, ${name}) => { 164 | chai.request(server) 165 | .get('/${name}/' + ${name}.id) 166 | .send(${name}) 167 | .end((err, res) => { 168 | res.should.have.status(200); 169 | res.body.should.be.a('object'); 170 | res.body.${name}.should.have.include.any.keys("${Object.keys(fake1).join('","')}"); 171 | res.body.${name}.should.have.property('_id').eql(${name}.id); 172 | done(); 173 | }); 174 | }); 175 | 176 | }); 177 | }); 178 | 179 | /* 180 | * Test the /GET route 181 | */ 182 | describe('/GET/ ${name}s', () => { 183 | it('it should GET many ${name}s', (done) => { 184 | const _fake5 = new ${uppercase(name)}(${JSON.stringify(fake5)}); 185 | _fake5.save((err, inner5) => { 186 | const _fake6 = new ${uppercase(name)}(${JSON.stringify(fake6)}); 187 | _fake6.save((err, inner6) => { 188 | chai.request(server) 189 | .get('/${name}s') 190 | .end((err, res) => { 191 | res.should.have.status(200); 192 | res.body.should.be.a('object'); 193 | res.body.${name}s.should.have.include.keys("docs", "totalDocs", "limit"); 194 | res.body.${name}s.should.have.property('docs') 195 | res.body.${name}s.docs.should.be.a('array'); 196 | res.body.${name}s.docs[0].should.have.include.any.keys("${Object.keys(fake1).join('","')}"); 197 | done(); 198 | }); 199 | }); 200 | }); 201 | }); 202 | }); 203 | 204 | /* 205 | * Test the /PUT/:id route 206 | */ 207 | describe('/PUT/:id ${name}', () => { 208 | it('it should UPDATE a ${name} given the id', (done) => { 209 | const _${name} = new ${uppercase(name)}(${JSON.stringify(fake3)}) 210 | _${name}.save((err, ${name}) => { 211 | chai.request(server) 212 | .put('/${name}/' + ${name}.id) 213 | .send(${JSON.stringify(fake3A)}) 214 | .end((err, res) => { 215 | res.should.have.status(200); 216 | res.body.should.be.a('object'); 217 | res.body.should.have.property('${name}') 218 | 219 | const stringCheck = String(res.body.${name}.${changedKey}) 220 | stringCheck.should.equal("${fake3A[changedKey]}"); 221 | done(); 222 | }); 223 | }); 224 | }); 225 | }); 226 | 227 | /* 228 | * Test the /DELETE/:id route 229 | */ 230 | describe('/DELETE/:id ${name}', () => { 231 | it('it should DELETE a ${name} given the id', (done) => { 232 | const _${name} = new ${uppercase(name)}(${JSON.stringify(fake4)}) 233 | _${name}.save((err, ${name}) => { 234 | chai.request(server) 235 | .delete('/${name}/' + ${name}.id) 236 | .end((err, res) => { 237 | res.should.have.status(200); 238 | res.body.should.be.a('object'); 239 | res.body.should.have.property("${name}"); 240 | res.body.${name}.should.be.a('object'); 241 | res.body.${name}.should.have.property('_id').eql(_${name}.id); 242 | done(); 243 | }); 244 | }); 245 | }); 246 | }); 247 | }); 248 | `; 249 | const pretty = beautify(code, { indent_size: 2, space_in_empty_paren: true }); 250 | fs.writeFileSync(testFile, pretty); 251 | }; 252 | -------------------------------------------------------------------------------- /api/tests/makeTests.js: -------------------------------------------------------------------------------- 1 | const mkdirp = require('mkdirp'); 2 | const makeRestTests = require('./makeRestTests'); 3 | 4 | module.exports = ({ schema, destination, logging }) => { 5 | schema = require(schema); // eslint-disable-line 6 | const isArray = Array.isArray(schema); 7 | const modelFolder = `${destination}/test`; 8 | mkdirp.sync(modelFolder); 9 | if (isArray) { 10 | schema.forEach((s) => makeRestTests({ schema: s, destination, logging })); 11 | } else { 12 | makeRestTests({ schema, destination, logging }); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /api/tests/randomPropertyChange.js: -------------------------------------------------------------------------------- 1 | const randChange = (schema, fake3A, doRequire = true) => { 2 | const k = Object.keys(fake3A); 3 | // if (doRequire) schema = require(schema).schema; // eslint-disable-line 4 | const genData = require('./genData'); 5 | const randomProperty = k[Math.floor(Math.random() * k.length)]; 6 | fake3A[randomProperty] = genData(schema[randomProperty].type, schema[randomProperty], schema[randomProperty].enum); 7 | // dont select unique properties to change or ones that don't get returned 8 | if (schema[randomProperty].unique || 9 | schema[randomProperty].select === false || 10 | schema[randomProperty].type === "ObjectId") { 11 | return randChange(schema, fake3A, false); 12 | } 13 | return { fake3A, changedKey: randomProperty }; 14 | }; 15 | module.exports = randChange; 16 | -------------------------------------------------------------------------------- /api/tests/subDocHelper.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = (obj) => { 3 | const genData = require('./genData'); 4 | if (!obj) throw new Error('subdocument is undefined'); 5 | let fake = {}; 6 | if (Array.isArray(obj)) { 7 | fake = []; 8 | obj.forEach((o) => { 9 | if (o.type) { 10 | fake.push(genData(o.type)); 11 | } else { 12 | Object.keys(o).forEach(key => { 13 | fake.push({ [key]: genData(o[key].type) }); 14 | }); 15 | } 16 | }); 17 | } else { 18 | Object.keys(obj).forEach(key => { 19 | fake[key] = genData(obj[key].type); 20 | }); 21 | } 22 | return fake; 23 | }; 24 | -------------------------------------------------------------------------------- /api/utils/createUpdateCode.js: -------------------------------------------------------------------------------- 1 | const { safeType } = require('../swagger'); 2 | 3 | module.exports = (schema, name) => { 4 | const uppercase = require('./uppercase'); 5 | const code = [`// Validation`]; 6 | code.push('// Model Validation '); 7 | Object.keys(schema).forEach(key => { 8 | if (Array.isArray(schema[key])) { 9 | code.push(`if (typeof ${key} !== 'undefined' && \n Array.isArray(${key})) { existing${uppercase(name)}.${key} = ${key}; }`); 10 | } else { 11 | if (schema[key].immutable) return; 12 | code.push(`if (typeof ${key} !== 'undefined' && \n typeof ${key} === "${safeType(schema[key].type).toLowerCase()}") {`); 13 | code.push(`existing${uppercase(name)}.${key} = ${key};`); 14 | code.push(`}`); 15 | } 16 | }); 17 | 18 | 19 | return code.join('\n'); 20 | }; 21 | -------------------------------------------------------------------------------- /api/utils/createValidationCode.js: -------------------------------------------------------------------------------- 1 | const uppercase = require('./uppercase'); 2 | const { safeType } = require('../swagger'); 3 | 4 | module.exports = (schema, name) => { 5 | const code = [`// Validate`]; 6 | Object.keys(schema).forEach(key => { 7 | if (Array.isArray(schema[key])) { 8 | code.push(`if (typeof ${key} !== 'undefined' && \n Array.isArray(${key})) { new${uppercase(name)}.${key} = ${key}; }`); 9 | } else { 10 | let extraValid = ""; 11 | if (safeType(schema[key].type).toLowerCase() === 'string') { 12 | extraValid = `&& ${key}`; 13 | } 14 | code.push(`if (typeof ${key} !== 'undefined' && \n typeof ${key} === "${safeType(schema[key].type).toLowerCase()}" ${extraValid}) {`); 15 | code.push(`new${uppercase(name)}.${key} = ${key};`); 16 | code.push(`}`); 17 | } 18 | }); 19 | return code.join('\n'); 20 | }; 21 | -------------------------------------------------------------------------------- /api/utils/extraParams.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | sort: { 3 | type: "object", 4 | example: { field: 'asc', test: -1 }, 5 | }, // query.sort({ field: 'asc', test: -1 }) 6 | select: { 7 | type: "object", 8 | example: { first_name: 1, last_name: 1 }, 9 | }, // query.select({ a: 1, b: 1 }); 10 | populate: { 11 | type: "string", 12 | default: "", 13 | example: "anotherModel" 14 | }, 15 | limit: { 16 | type: "number", 17 | default: 10, 18 | example: 25 19 | }, 20 | page: { 21 | type: "number", 22 | default: 0, 23 | example: 1 24 | }, 25 | search: { 26 | type: "string", 27 | default: "", 28 | example: "search string" 29 | }, 30 | filters: { 31 | type: "string", 32 | default: "", 33 | example: 'stringified array [{"column":{"title":"Name","field":"name","type":"…Sort":"asc","id":0}},"operator":"=","value":"1"}]"' 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /api/utils/getSchemaQueryDefinitions.js: -------------------------------------------------------------------------------- 1 | module.exports = (schema) => { 2 | const { safeType } = require('../swagger'); 3 | const code = []; 4 | Object.keys(schema).forEach(key => { 5 | code.push(`* - in: query`); 6 | code.push(`* name: ${key}`); 7 | // code.push(`* style: deepObject`); 8 | code.push(`* description: object supports mongodb queries on objects. see https://mongoosejs.com/docs/api.html#query_Query or ${safeType(schema[key].type).toLowerCase()} ${key} `); 9 | code.push(`* schema:`); 10 | code.push(`* oneOf:`); 11 | code.push(`* - type: ${safeType(schema[key].type).toLowerCase()}`); 12 | code.push(`* - type: object`); 13 | // code.push(`* type: ${safeType(schema[key].type).toLowerCase()}`); 14 | code.push(`* example: { $gte: 5 } `); 15 | 16 | }); 17 | return code; 18 | }; 19 | -------------------------------------------------------------------------------- /api/utils/getSearchCode.js: -------------------------------------------------------------------------------- 1 | const uppercase = require('./uppercase'); 2 | const beautify = require('js-beautify').js; 3 | 4 | const searchCodeByType = (type, key, isArray = false) => { 5 | const returnCode = []; 6 | 7 | switch (type.toLowerCase()) { 8 | case 'number': 9 | if (isArray) { 10 | returnCode.push(`if (!isNaN(search)) { find.$or.push({ ${key}: { $in: [search] } }); }`); 11 | } else { 12 | returnCode.push(`if (!isNaN(search)) { find.$or.push({ ${key}: { $eq: search } }); }`); 13 | } 14 | break; 15 | case 'boolean': 16 | if (isArray) { 17 | returnCode.push(`if (search === 'true' || search === 'false') { find.$or.push({ ${key}: { $in: [search === 'true' ? true : false] } }); }`); 18 | } else { 19 | returnCode.push(`if (search === 'true' || search === 'false') { find.$or.push({ ${key}: search === 'true' ? true : false }); }`); 20 | } 21 | break; 22 | case 'objectid': 23 | case 'string': 24 | if (isArray) { 25 | console.log('ISARRAY', isArray, key) 26 | returnCode.push(`find.$or.push({ ${key}: { $in: [search] } })`); 27 | } else { 28 | returnCode.push(`find.$or.push({ ${key}: { $regex: new RegExp(search, "ig") } })`); 29 | } 30 | break; 31 | default: 32 | } 33 | return returnCode; 34 | }; 35 | 36 | module.exports = (schema, name) => { 37 | let code = ['find.$or = [];']; 38 | Object.keys(schema.schema).forEach((key) => { 39 | const item = schema.schema[key]; 40 | if (item.type) { 41 | code = code.concat(searchCodeByType(item.type, key)); 42 | } else if (Array.isArray(item)) { 43 | item.forEach((i) => { 44 | if (i.type) { 45 | code = code.concat(searchCodeByType(i.type, `${key}`, true)); 46 | } else { 47 | Object.keys(i).forEach((_key) => { 48 | code = code.concat(searchCodeByType(i[_key].type, `"${key}.${_key}"`)); 49 | }); 50 | } 51 | }) 52 | 53 | } else { 54 | Object.keys(item).forEach((_key) => { 55 | code = code.concat(searchCodeByType(item[_key].type, `"${key}.${_key}"`)); 56 | }); 57 | } 58 | }); 59 | const pretty = beautify(code.join('\n'), { indent_size: 2, space_in_empty_paren: true }); 60 | return pretty; 61 | }; 62 | -------------------------------------------------------------------------------- /api/utils/getValidationCode.js: -------------------------------------------------------------------------------- 1 | const uppercase = require('./uppercase'); 2 | const { safeType, safeDefault } = require('../swagger'); 3 | 4 | module.exports = (schema) => { 5 | const extras = require('./extraParams'); 6 | const returnValueBasedOnType = require('./returnValueBasedOnType'); 7 | const code = [`// Validation`]; 8 | 9 | // pagination, etc. 10 | const extraKeys = Object.keys(extras); 11 | if (extraKeys.length > 0) { 12 | code.push('// Pagination '); 13 | code.push(`if (sort && typeof sort === 'string') sort = JSON.parse(sort);`); 14 | extraKeys.forEach(key => { 15 | code.push(`if (typeof ${key} !== 'undefined' && ${key} !== '') {`); 16 | code.push(`where.${key} = ${returnValueBasedOnType(extras[key].type, key)};`); 17 | if (typeof extras[key].default !== 'undefined') { 18 | code.push(`} else {`); 19 | code.push(` where.${key} = ${safeDefault(extras[key].default)};`); 20 | code.push(`}`); 21 | } else { 22 | code.push('}'); 23 | } 24 | }); 25 | } 26 | // code.push(`where.limit = limit;`); 27 | code.push(`where.offset = page * limit;`); 28 | code.push('// Model Validation '); 29 | Object.keys(schema).forEach(key => { 30 | code.push(`if (typeof ${key} !== 'undefined') {`); 31 | code.push(` try {`); 32 | code.push(`find.${key} = JSON.parse(${key})`); 33 | code.push(` } catch (e) {`); 34 | code.push(`find.${key} = ${returnValueBasedOnType(schema[key].type, key)};`); 35 | code.push(`}`); 36 | code.push(`}`); 37 | }); 38 | return code.join('\n'); 39 | }; 40 | -------------------------------------------------------------------------------- /api/utils/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | uppercase: require('./uppercase'), 3 | createValidationCode: require('./createValidationCode'), 4 | getValidationCode: require('./getValidationCode'), 5 | createUpdateCode: require('./createUpdateCode'), 6 | sugarGenerated: require('./sugarGenerated'), 7 | extraParams: require('./extraParams'), 8 | validateSchema: require('./validateSchema'), 9 | getSchemaQueryDefinitions: require('./getSchemaQueryDefinitions'), 10 | getSearchCode: require('./getSearchCode'), 11 | }; 12 | -------------------------------------------------------------------------------- /api/utils/returnValueBasedOnType.js: -------------------------------------------------------------------------------- 1 | module.exports = (type, key) => { 2 | if (!type) return key; 3 | switch (type.toLowerCase()) { 4 | case 'number': 5 | return `isNaN(${key}) ? ${key} : Number(${key})`; 6 | case 'boolean': 7 | return `${key} === 'false' ? false : true`; 8 | case 'objectid': 9 | default: 10 | return key; 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /api/utils/sugarGenerated.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment'); 2 | 3 | module.exports = () => `/* 4 | * Generated By SugarKubes ${moment().format('MMM DD YYYY')} 5 | */ 6 | ` 7 | -------------------------------------------------------------------------------- /api/utils/uppercase.js: -------------------------------------------------------------------------------- 1 | module.exports = (str) => str.charAt(0).toUpperCase() + str.slice(1); 2 | -------------------------------------------------------------------------------- /api/utils/validateSchema.js: -------------------------------------------------------------------------------- 1 | module.exports = (schema) => { 2 | try { 3 | schema = require(schema); // eslint-disable-line 4 | if (Array.isArray(schema)) { 5 | let isValid = false; 6 | schema.forEach((s) => { 7 | // schema name required 8 | if (!s.name) isValid = "Each schema requires a name"; 9 | if (s.schema) isValid = true; 10 | }); 11 | return isValid; 12 | } else { 13 | // schema name required 14 | if (!schema.name) return "Schema requires a name"; 15 | if (schema.schema) return true; 16 | } 17 | return false; 18 | } catch (e) { 19 | if (e.message && e.message.indexOf('Unexpected token')) { 20 | let msg = e.message.split('Unexpected token'); 21 | if (msg && msg[1]) { 22 | msg = msg[1].split('at position '); 23 | return `Uh oh, JSON syntax mistake. Check line ${msg[1]}.`; 24 | } 25 | } 26 | if (e.message && e.message.indexOf('Cannot find module') !== -1) { 27 | return "Can't find this json file. Does it exist?"; 28 | } 29 | return false; 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /app/components/createChipComponent.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const mkdirp = require('mkdirp'); 3 | const { uppercase } = require('../../api/utils'); 4 | const getReactState = require('./getReactState'); 5 | const textFieldsByType = require('./textFieldsByType'); 6 | 7 | module.exports = async ({ destination }) => { 8 | const fileName = `${destination}/components/Shared/Chips.js`; 9 | 10 | const componentName = `Chips`; 11 | 12 | const code = ` 13 | import React, { Component } from 'react'; 14 | import PropTypes from 'prop-types'; 15 | import { withStyles } from '@material-ui/core/styles'; 16 | import Avatar from '@material-ui/core/Avatar'; 17 | import Chip from '@material-ui/core/Chip'; 18 | 19 | const styles = (theme) => ({ 20 | 21 | }); 22 | 23 | class ${componentName} extends Component { 24 | static defaultProps = { 25 | items: [], 26 | chipClick: () => {}, 27 | chipDelete: () => {}, 28 | } 29 | 30 | 31 | render() { 32 | const { classes, items } = this.props; 33 | return ( 34 |
35 | {items.map((itemString, index) => ( 36 | 44 | ))} 45 |
46 | ) 47 | } 48 | 49 | } 50 | 51 | ${componentName}.propTypes = { 52 | classes: PropTypes.object.isRequired, 53 | items: PropTypes.array, 54 | chipClick: PropTypes.func, 55 | chipDelete: PropTypes.func, 56 | }; 57 | 58 | export default withStyles(styles, { withTheme: true })(${componentName}); 59 | `; 60 | fs.writeFileSync(fileName, code); 61 | }; 62 | -------------------------------------------------------------------------------- /app/components/createChipInputComponent.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const mkdirp = require('mkdirp'); 3 | const { uppercase } = require('../../api/utils'); 4 | const getReactState = require('./getReactState'); 5 | const textFieldsByType = require('./textFieldsByType'); 6 | 7 | module.exports = async ({ destination }) => { 8 | const fileName = `${destination}/components/Shared/ChipForm.js`; 9 | 10 | const componentName = `ChipsWithInput`; 11 | 12 | const code = ` 13 | import React, { Component } from 'react'; 14 | import PropTypes from 'prop-types'; 15 | import { withStyles } from '@material-ui/core/styles'; 16 | import Avatar from '@material-ui/core/Avatar'; 17 | import Chips from './Chips'; 18 | import TextField from '@material-ui/core/TextField'; 19 | import Button from '@material-ui/core/Button'; 20 | 21 | const styles = (theme) => ({ 22 | textField: { 23 | marginLeft: theme.spacing(1), 24 | marginRight: theme.spacing(1), 25 | width: 200, 26 | }, 27 | chip: { 28 | margin: theme.spacing(1), 29 | }, 30 | }); 31 | 32 | class ${componentName} extends Component { 33 | static defaultProps = { 34 | items: [], 35 | label: '', 36 | chipClick: () => {}, 37 | chipDelete: () => {}, 38 | value: '', 39 | onChange: () => {}, 40 | onChipAddClick: () => {}, 41 | } 42 | 43 | 44 | render() { 45 | const { classes } = this.props; 46 | return ( 47 |
48 | 55 | 56 | 61 | 62 | 67 |
68 | ) 69 | } 70 | 71 | } 72 | 73 | ${componentName}.propTypes = { 74 | classes: PropTypes.object.isRequired, 75 | items: PropTypes.array, 76 | chipClick: PropTypes.func, 77 | chipDelete: PropTypes.func, 78 | value: PropTypes.string, 79 | onChange: PropTypes.func, 80 | onChipAddClick: PropTypes.func, 81 | label: PropTypes.string, 82 | }; 83 | 84 | export default withStyles(styles, { withTheme: true })(${componentName}); 85 | `; 86 | fs.writeFileSync(fileName, code); 87 | }; 88 | -------------------------------------------------------------------------------- /app/components/createForm.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const mkdirp = require('mkdirp'); 3 | const { uppercase } = require('../../api/utils'); 4 | const getReactState = require('./getReactState'); 5 | const textFieldsByType = require('./textFieldsByType'); 6 | 7 | module.exports = async ({ destination, schema }) => { 8 | const dir = `${destination}/components/${uppercase(schema.name)}`; 9 | mkdirp.sync(dir); 10 | const fileName = `${dir}/Create${uppercase(schema.name)}Form.js`; 11 | 12 | const code = ` 13 | import React, { Component } from 'react'; 14 | import PropTypes from 'prop-types'; 15 | 16 | import apiCall from '../../src/apiCall'; 17 | 18 | import { ThemeProvider } from '@material-ui/styles'; 19 | import { withStyles } from '@material-ui/styles'; 20 | import CssBaseline from '@material-ui/core/CssBaseline'; 21 | 22 | import Typography from '@material-ui/core/Typography'; 23 | import TextField from '@material-ui/core/TextField'; 24 | import Button from '@material-ui/core/Button'; 25 | import Switch from '@material-ui/core/Switch'; 26 | import Radio from '@material-ui/core/Radio'; 27 | import RadioGroup from '@material-ui/core/RadioGroup'; 28 | 29 | import FormGroup from '@material-ui/core/FormGroup'; 30 | import FormControlLabel from '@material-ui/core/FormControlLabel'; 31 | import FormControl from '@material-ui/core/FormControl'; 32 | import FormHelperText from '@material-ui/core/FormHelperText'; 33 | import FormLabel from '@material-ui/core/FormLabel'; 34 | 35 | import Snackbar from '../Shared/Snackbar'; 36 | import ChipForm from '../Shared/ChipForm'; 37 | 38 | const styles = theme => ({ 39 | container: { 40 | display: 'flex', 41 | flexWrap: 'wrap', 42 | maxWidth: 600, 43 | alignItems: 'center', 44 | justifyContent: 'center', 45 | margin: '0 auto', 46 | }, 47 | fields: { 48 | display: 'flex', 49 | flexDirection: 'row', 50 | flexWrap: 'wrap', 51 | alignItems: "flex-start", 52 | justifyContent: "space-between", 53 | }, 54 | form: { 55 | // flexDirection: 'column', 56 | // display: 'flex', 57 | maxWidth: 600, 58 | alignItems: 'center', 59 | justifyContent: 'center', 60 | }, 61 | radioGroup: { 62 | flexDirection: 'row', 63 | display: 'flex', 64 | }, 65 | switch: { 66 | // width: 200, 67 | // minHeight: 56, 68 | // margin: '16px 8px 8px 8px', 69 | }, 70 | switchRoot: { 71 | root: { 72 | width: 42, 73 | height: 26, 74 | padding: 0, 75 | margin: theme.spacing(1), 76 | }, 77 | }, 78 | radioRoot: { 79 | width: 200, 80 | minHeight: 56, 81 | margin: '16px 8px 8px 8px', 82 | }, 83 | switchLabel: { 84 | display: 'flex', 85 | flexDirection: 'row', 86 | alignItems: 'center', 87 | justifyContent: 'flex-start', 88 | width: 200, 89 | minHeight: 56, 90 | margin: '16px 8px 8px 8px', 91 | }, 92 | button: { 93 | margin: theme.spacing(1), 94 | }, 95 | textField: { 96 | marginLeft: theme.spacing(1), 97 | marginRight: theme.spacing(1), 98 | width: 200, 99 | }, 100 | dense: { 101 | marginTop: 19, 102 | }, 103 | menu: { 104 | width: 200, 105 | }, 106 | }); 107 | 108 | class Create${uppercase(schema.name)}Form extends Component { 109 | 110 | constructor(props) { 111 | super(props); 112 | this.state = ${getReactState(schema, true)} 113 | this.state.snackbar = { 114 | variant: 'success', 115 | message: '', 116 | } 117 | this.schema = ${JSON.stringify(schema.schema)} 118 | } 119 | 120 | handleChange = (name, type = '') => event => { 121 | let value = event.target.value; 122 | 123 | if (type && type === 'boolean' && typeof event.target.checked !== 'undefined') { 124 | value = event.target.checked; 125 | } 126 | if (type && type === 'number') { 127 | value = typeof value === 'string' ? Number(value) : value; 128 | } 129 | 130 | if (name.indexOf('.') === -1) { 131 | this.setState({ [name] : value }); 132 | } else { 133 | name = name.split('.') 134 | const item = this.state[name[0]]; 135 | item[name[1]] = value; 136 | this.setState({ [name[0]]: item }); 137 | } 138 | } 139 | 140 | chipClick = (name) => (value) => (event) => { 141 | console.log('chip click ', name, value, event); 142 | } 143 | 144 | chipDelete = (name) => (value, index) => (event) => { 145 | const copy = this.state[name].slice(); 146 | copy.splice(index, 1); 147 | console.log('COPY', copy) 148 | this.setState({ [name]: copy }); 149 | } 150 | 151 | errorString = (e) => { 152 | const snackbar = Object.assign({}, this.state); 153 | snackbar.variant = 'error'; 154 | snackbar.message = e.message ? e.message : e 155 | this.setState({ snackbar }); 156 | } 157 | 158 | onChipAddClick = (name) => (e) => { 159 | console.log('on chip add click ', name, this.state[\`\${name}StringValue\`]); 160 | const _copy = this.state[\`\${name}\`]; 161 | _copy.push(this.state[\`\${name}StringValue\`]); 162 | this.setState({ [\`\${name}StringValue\`]: "", [name]: _copy }); 163 | } 164 | 165 | submit = async (e) => { 166 | e.preventDefault(); 167 | try { 168 | const res = await apiCall({ url: '${schema.name}', method: 'POST', data: this.state }); 169 | if (res && res.error) return this.errorString(res.error); 170 | else return this.setState({ snackbar: { variant: 'success', message: '${uppercase(schema.name)} Created.' }}); 171 | } catch (e) { 172 | console.error('submit error', e); 173 | this.errorString(e); 174 | } 175 | } 176 | 177 | onSnackbarClose = (e) => { 178 | this.setState({ snackbar: { message: '', variant: 'info' }}) 179 | } 180 | 181 | render () { 182 | const { classes } = this.props; 183 | const { snackbar, ${getReactState(schema, true, true)} } = this.state; 184 | return ( 185 |
186 |
187 | 188 | ${textFieldsByType(schema, destination)} 189 | 190 | 198 |
199 | 204 |
205 | ); 206 | } 207 | } 208 | 209 | Create${uppercase(schema.name)}Form.propTypes = { 210 | classes: PropTypes.object.isRequired, 211 | }; 212 | 213 | export default withStyles(styles, { withTheme: true })(Create${uppercase(schema.name)}Form); 214 | `; 215 | fs.writeFileSync(fileName, code); 216 | }; 217 | -------------------------------------------------------------------------------- /app/components/createSnackbar.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const mkdirp = require('mkdirp'); 3 | const { uppercase } = require('../../api/utils'); 4 | const getReactState = require('./getReactState'); 5 | const textFieldsByType = require('./textFieldsByType'); 6 | 7 | module.exports = async ({ destination, schema }) => { 8 | const dir = `${destination}/components/Shared`; 9 | mkdirp.sync(dir); 10 | const fileName = `${dir}/Snackbar.js`; 11 | 12 | const code = ` 13 | import React, { Component } from 'react'; 14 | import PropTypes from 'prop-types'; 15 | import clsx from 'clsx'; 16 | import Button from '@material-ui/core/Button'; 17 | 18 | import IconButton from '@material-ui/core/IconButton'; 19 | import Snackbar from '@material-ui/core/Snackbar'; 20 | import SnackbarContent from '@material-ui/core/SnackbarContent'; 21 | import WarningIcon from '@material-ui/icons/Warning'; 22 | import { withStyles } from '@material-ui/core/styles'; 23 | 24 | import CheckCircleIcon from '@material-ui/icons/CheckCircle'; 25 | import ErrorIcon from '@material-ui/icons/Error'; 26 | import InfoIcon from '@material-ui/icons/Info'; 27 | import CloseIcon from '@material-ui/icons/Close'; 28 | 29 | import green from '@material-ui/core/colors/green'; 30 | import amber from '@material-ui/core/colors/amber'; 31 | 32 | const variantIcon = { 33 | success: CheckCircleIcon, 34 | warning: WarningIcon, 35 | error: ErrorIcon, 36 | info: InfoIcon, 37 | }; 38 | 39 | 40 | const styles = theme => ({ 41 | success: { 42 | backgroundColor: green[600], 43 | }, 44 | error: { 45 | backgroundColor: theme.palette.error.dark, 46 | }, 47 | info: { 48 | backgroundColor: theme.palette.primary.dark, 49 | }, 50 | warning: { 51 | backgroundColor: amber[700], 52 | }, 53 | snackbar: { 54 | 55 | }, 56 | message: { 57 | display: 'flex', 58 | flexDirection: 'row', 59 | alignItems: 'center', 60 | justifyContent: 'center' 61 | } 62 | }); 63 | 64 | class SharedSnackbar extends Component { 65 | 66 | static defaultProps = { 67 | message: '', 68 | variant: 'info', 69 | handleClose: () => {}, 70 | } 71 | 72 | render () { 73 | const { handleClose, classes, className, message, onClose, variant, ...other } = this.props; 74 | const Icon = variantIcon[variant]; 75 | return ( 76 | 85 | 90 | 91 | {message} 92 | 93 | } 94 | action={[ 95 | 96 | 97 | , 98 | ]} 99 | {...other} 100 | onClose={handleClose} 101 | /> 102 | 103 | ); 104 | } 105 | } 106 | 107 | SharedSnackbar.propTypes = { 108 | classes: PropTypes.object.isRequired, 109 | variant: PropTypes.string, 110 | message: PropTypes.string, 111 | handleClose: PropTypes.func, 112 | }; 113 | 114 | export default withStyles(styles, { withTheme: true })(SharedSnackbar); 115 | `; 116 | fs.writeFileSync(fileName, code); 117 | }; 118 | -------------------------------------------------------------------------------- /app/components/createTable.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const mkdirp = require('mkdirp'); 3 | const { uppercase } = require('../../api/utils'); 4 | const getReactState = require('./getReactState'); 5 | const getTableColumns = require('./getTableColumns'); 6 | const textFieldsByType = require('./textFieldsByType'); 7 | const beautify = require('js-beautify').html; 8 | 9 | module.exports = async ({ destination, schema }) => { 10 | const dir = `${destination}/components/${uppercase(schema.name)}`; 11 | mkdirp.sync(dir); 12 | const fileName = `${dir}/${uppercase(schema.name)}Table.js`; 13 | 14 | const code = ` 15 | import React, { Component } from 'react'; 16 | import PropTypes from 'prop-types'; 17 | import MaterialTable from "material-table"; 18 | 19 | import { withStyles } from '@material-ui/core/styles'; 20 | 21 | import AddBox from '@material-ui/icons/AddBox'; 22 | import ArrowUpward from '@material-ui/icons/ArrowUpward'; 23 | import Check from '@material-ui/icons/Check'; 24 | import ChevronLeft from '@material-ui/icons/ChevronLeft'; 25 | import ChevronRight from '@material-ui/icons/ChevronRight'; 26 | import Clear from '@material-ui/icons/Clear'; 27 | import DeleteOutline from '@material-ui/icons/DeleteOutline'; 28 | import Edit from '@material-ui/icons/Edit'; 29 | import FilterList from '@material-ui/icons/FilterList'; 30 | import FirstPage from '@material-ui/icons/FirstPage'; 31 | import LastPage from '@material-ui/icons/LastPage'; 32 | import Remove from '@material-ui/icons/Remove'; 33 | import SaveAlt from '@material-ui/icons/SaveAlt'; 34 | import Search from '@material-ui/icons/Search'; 35 | import ViewColumn from '@material-ui/icons/ViewColumn'; 36 | import Refresh from '@material-ui/icons/RefreshOutlined'; 37 | 38 | 39 | import apiCall from '../../src/apiCall'; 40 | 41 | import Snackbar from '../Shared/Snackbar'; 42 | 43 | const styles = theme => ({ 44 | 45 | }); 46 | 47 | const tableIcons = { 48 | Add: AddBox, 49 | Check: Check, 50 | Clear: Clear, 51 | Delete: DeleteOutline, 52 | DetailPanel: ChevronRight, 53 | Edit: Edit, 54 | Export: SaveAlt, 55 | Filter: FilterList, 56 | FirstPage: FirstPage, 57 | LastPage: LastPage, 58 | NextPage: ChevronRight, 59 | PreviousPage: ChevronLeft, 60 | ResetSearch: Clear, 61 | Search: Search, 62 | SortArrow: ArrowUpward, 63 | ThirdStateCheck: Remove, 64 | ViewColumn: ViewColumn, 65 | Refresh: Refresh, 66 | }; 67 | 68 | class ${uppercase(schema.name)}Table extends Component { 69 | 70 | constructor(props) { 71 | super(props); 72 | this.state = ${getReactState(schema, true)} 73 | this.state.snackbar = { 74 | variant: 'success', 75 | message: '', 76 | } 77 | this.state.docs = []; 78 | this.state.limit = 10; 79 | this.state.totalDocs = 0; 80 | this.state.offset = 0; 81 | // this.schema = ${JSON.stringify(schema.schema)} 82 | this.tableRef = React.createRef(); 83 | 84 | } 85 | 86 | componentWillMount () { 87 | this.fetchData(); 88 | } 89 | 90 | fetchData = async (query) => { 91 | if (!query) query = {}; 92 | query.limit = query.pageSize || 10; 93 | const res = await apiCall({ url: '${schema.name}s', method: 'GET', params: query }); 94 | 95 | // prevent arrays from blowing the page 96 | res.${schema.name}s.docs = res.${schema.name}s.docs.map((doc) => { 97 | const _doc = Object.assign({}, doc); 98 | Object.keys(_doc).forEach((key) => { 99 | if (Array.isArray(_doc[key])) _doc[key] = JSON.stringify(_doc[key]); 100 | }); 101 | return _doc; 102 | }) 103 | return { 104 | data: res.${schema.name}s.docs, 105 | page: res.${schema.name}s.offset / res.${schema.name}s.limit, 106 | totalCount: res.${schema.name}s.totalDocs, 107 | } 108 | } 109 | 110 | onSnackbarClose = (e) => { 111 | this.setState({ snackbar: { message: '', variant: 'info' }}) 112 | } 113 | 114 | onRowAdd = async (newData) => { 115 | const res = await apiCall({ url: "${schema.name}", method: 'POST', data: newData }); 116 | if (!res.error) this.setState({ snackbar: { variant: 'success', message: '${uppercase(schema.name)} Created.' }}); 117 | else this.setState({ snackbar: { variant: 'error', message: res.error }}); 118 | } 119 | onRowUpdate = async (newData, oldData) => { 120 | const res = await apiCall({ url: \`${schema.name}/\${oldData._id}\`, method: 'PUT', data: newData }); 121 | if (!res.error) this.setState({ snackbar: { variant: 'success', message: '${uppercase(schema.name)} Updated.' }}); 122 | else this.setState({ snackbar: { variant: 'error', message: res.error }}); 123 | } 124 | onRowDelete = async (oldData) => { 125 | const res = await apiCall({ url: \`${schema.name}/\${oldData._id}\`, method: 'DELETE', }) 126 | if (!res.error) this.setState({ snackbar: { variant: 'success', message: '${uppercase(schema.name)} Deleted.' }}); 127 | else this.setState({ snackbar: { variant: 'error', message: res.error }}); 128 | } 129 | 130 | render () { 131 | /* 132 | 133 | Cant edit / delete when selection: true 134 | 135 | https://github.com/mbrn/material-table/issues/648 136 | 137 | */ 138 | 139 | 140 | const { classes } = this.props; 141 | const { snackbar, ${getReactState(schema, true, true)} } = this.state; 142 | return ( 143 |
144 |
145 |
146 | { 169 | // console.log('ROWDATA', rowData) 170 | // const areUnique = []; 171 | // const columns = ${getTableColumns(schema)}; 172 | // console.log('COLUMNS', columns) 173 | // columns.forEach((column) => column.readonly ? areUnique.push(column.field) : ""); 174 | // console.log('AREUNIQUE', areUnique) 175 | return true; 176 | }, 177 | isDeletable: () => true, 178 | }} 179 | actions={[ 180 | { 181 | icon: Refresh, 182 | tooltip: 'Refresh Data', 183 | isFreeAction: true, 184 | onClick: () => this.tableRef.current && this.tableRef.current.onQueryChange(), 185 | } 186 | ]} 187 | /> 188 |
189 |
190 | 195 |
196 | ); 197 | } 198 | } 199 | 200 | ${uppercase(schema.name)}Table.propTypes = { 201 | classes: PropTypes.object.isRequired, 202 | }; 203 | 204 | export default withStyles(styles, { withTheme: true })(${uppercase(schema.name)}Table); 205 | `; 206 | fs.writeFileSync(fileName, code); 207 | }; 208 | -------------------------------------------------------------------------------- /app/components/getReactState.js: -------------------------------------------------------------------------------- 1 | const typeToValue = require('./typeToValue'); 2 | 3 | module.exports = (schema, stringify = false, keysOnly = false) => { 4 | const obj = {}; 5 | const nestedKeys = {}; 6 | Object.keys(schema.schema).forEach((key) => { 7 | let val = ""; 8 | if (!schema.schema[key].type) { 9 | if (Array.isArray(schema.schema[key])) { 10 | val = []; 11 | schema.schema[key].forEach((arrayObject, index) => { 12 | if (arrayObject.type) { 13 | const item = schema.schema[key][index]; 14 | obj[`${key}StringValue`] = item.default ? item.default : typeToValue(item.type); 15 | val.push(item.default ? item.default : typeToValue(item.type)); 16 | } else { 17 | const _sub = {}; 18 | Object.keys(arrayObject).forEach((_key) => { 19 | obj[`${_key}StringValue`] = typeToValue(arrayObject[_key].type); 20 | _sub[key] = arrayObject[_key].default ? arrayObject[_key].default : typeToValue(arrayObject[_key].type); 21 | }); 22 | } 23 | }); 24 | } else { 25 | val = {}; 26 | Object.keys(schema.schema[key]).forEach((_key) => { 27 | val[_key] = schema.schema[key][_key].default ? schema.schema[key][_key].default : typeToValue(schema.schema[key][_key].type); 28 | if (!nestedKeys[key]) nestedKeys[key] = []; 29 | nestedKeys[key].push(_key); 30 | }); 31 | } 32 | 33 | } else { 34 | val = schema.schema[key].default ? schema.schema[key].default : typeToValue(schema.schema[key].type); 35 | } 36 | obj[key] = val; 37 | }); 38 | 39 | if (keysOnly) { 40 | const keysArray = Object.keys(obj); 41 | Object.keys(nestedKeys).forEach((nestedKey) => { 42 | const idx = keysArray.indexOf(nestedKey); 43 | const nestedKeyString = `${nestedKey}: { ${nestedKeys[nestedKey].join(', ')} }`; 44 | keysArray[idx] = nestedKeyString; 45 | }); 46 | return keysArray.join(', '); 47 | } 48 | return stringify ? JSON.stringify(obj) : obj; 49 | }; 50 | -------------------------------------------------------------------------------- /app/components/getTableColumns.js: -------------------------------------------------------------------------------- 1 | const typeToValue = require('./typeToValue'); 2 | const { uppercase } = require('../../api/utils'); 3 | 4 | 5 | const tableColumnsByType = (_type, schema, k, _title = "") => { 6 | let type = 'string'; 7 | switch (_type) { 8 | case 'Number': 9 | type = 'numeric'; 10 | break; 11 | case 'Boolean': 12 | type = 'boolean'; 13 | break; 14 | default: 15 | 16 | } 17 | let title = _title ? _title : uppercase(String(k)); 18 | if (schema.schema[k].required) title += '*'; 19 | const obj = { 20 | title, 21 | field: k, 22 | type, 23 | editable: schema.schema[k].unique ? 'always' : 'never' 24 | // customFilterAndSearch: "this.customFilterAndSearch", 25 | }; 26 | // if (schema.schema[k].default) { 27 | // obj.emptyValue = schema.schema[k].default; 28 | // } 29 | if (schema.schema[k].enum) { 30 | obj.lookup = {}; 31 | schema.schema[k].enum.forEach(item => obj.lookup[item] = uppercase(item)); 32 | } 33 | return obj; 34 | }; 35 | 36 | module.exports = (schema, stringify = false, keysOnly = false) => { 37 | const columns = []; 38 | const keys = Object.keys(schema.schema); 39 | keys.forEach((k) => { 40 | const _type = schema.schema[k].type; 41 | if (_type) { 42 | const obj = tableColumnsByType(_type, schema, k); 43 | columns.push(obj); 44 | } else if (Array.isArray(schema.schema[k])) { 45 | schema.schema[k].forEach((item, idx) => { 46 | if (item.type) { 47 | const obj = tableColumnsByType(item.type, schema, `${k}`); 48 | columns.push(obj); 49 | } else { 50 | Object.keys(item).forEach((subKey) => { 51 | const obj = tableColumnsByType(item[subKey].type, { schema: item }, subKey, `${k} - ${subKey}`); 52 | columns.push(obj); 53 | }); 54 | } 55 | }); 56 | } else { 57 | Object.keys(schema.schema[k]).forEach((_key) => { 58 | const _subType = schema.schema[k][_key].type; 59 | let subType = 'string'; 60 | switch (_subType) { 61 | case 'Number': 62 | subType = 'numeric'; 63 | break; 64 | default: 65 | } 66 | 67 | let _title = `${uppercase(k)} ${uppercase(_key)}`; 68 | if (schema.schema[k][_key].required) _title += '*'; 69 | const _obj = { 70 | title: _title, 71 | field: `${k}.${_key}`, 72 | type: subType, 73 | readonly: schema.schema[k][_key].unique ? true : false, 74 | // customFilterAndSearch: "this.customFilterAndSearch", 75 | }; 76 | // if (schema.schema[k][_key].default) { 77 | // _obj.emptyValue = schema.schema[k].default; 78 | // } 79 | columns.push(_obj); 80 | }); 81 | } 82 | }); 83 | // [ 84 | // { title: "Adı", field: "name" }, 85 | // { title: "Soyadı", field: "surname" }, 86 | // { title: "Doğum Yılı", field: "birthYear", type: "numeric" }, 87 | // { 88 | // title: "Doğum Yeri", 89 | // field: "birthCity", 90 | // lookup: { 34: "İstanbul", 63: "Şanlıurfa" } 91 | // } 92 | // ] 93 | return JSON.stringify(columns); 94 | }; 95 | -------------------------------------------------------------------------------- /app/components/makeComponents.js: -------------------------------------------------------------------------------- 1 | const createForm = require('./createForm'); 2 | const createSnackbar = require('./createSnackbar'); 3 | const createTable = require('./createTable'); 4 | const createChipComponent = require('./createChipComponent'); 5 | const createChipInputComponent = require('./createChipInputComponent'); 6 | 7 | module.exports = ({ schema, destination }) => { 8 | schema = require(schema); // eslint-disable-line 9 | if (Array.isArray(schema)) { 10 | schema.forEach((s) => { 11 | createForm({ destination, schema: s }); 12 | createTable({ destination, schema: s }); 13 | }); 14 | } else { 15 | createForm({ destination, schema }); 16 | createTable({ destination, schema }); 17 | } 18 | 19 | createSnackbar({ destination, schema }); 20 | createChipComponent({ destination, schema }); 21 | createChipInputComponent({ destination, schema }); 22 | }; 23 | -------------------------------------------------------------------------------- /app/components/tagsComponent.js: -------------------------------------------------------------------------------- 1 | module.exports = () => { 2 | return ''; 3 | }; 4 | -------------------------------------------------------------------------------- /app/components/textFieldByType.js: -------------------------------------------------------------------------------- 1 | const { uppercase } = require('../../api/utils'); 2 | 3 | const radio = (name, type, fullObject) => { 4 | const enumArray = fullObject.enum.map((item) => (`} label="${uppercase(item)}" />`)); 5 | return ` 6 | 7 | ${uppercase(name)} 8 | 15 | ${enumArray.join('\n')} 16 | 17 | 18 | `; 19 | }; 20 | 21 | module.exports = (name, type, fullObject) => { 22 | // error={this.state.${name}Error} 23 | 24 | let textFieldType = 'text'; 25 | const htmlType = fullObject.htmlType; 26 | if (!htmlType) { 27 | textFieldType = type === 'Number' ? 'number' : 'text'; 28 | } else { 29 | textFieldType = htmlType; 30 | } 31 | switch (type) { 32 | case 'String': 33 | case 'Number': 34 | if (htmlType && htmlType === 'radio') return radio(name, type, fullObject); 35 | return ` 36 | `; 46 | case 'Boolean': 47 | return ` 48 |
49 | 63 | } 64 | label="${uppercase(name)}" 65 | /> 66 |
67 | 68 | `; 69 | case 'Array': 70 | return ` 71 | 80 | `; 81 | default: 82 | 83 | } 84 | 85 | 86 | }; 87 | 88 | 89 | // 97 | -------------------------------------------------------------------------------- /app/components/textFieldsByType.js: -------------------------------------------------------------------------------- 1 | const textFieldByType = require('./textFieldByType'); 2 | const tagsComponent = require('./tagsComponent'); 3 | 4 | module.exports = (schema, destination) => { 5 | // textFieldByType(type, name) 6 | const code = []; 7 | Object.keys(schema.schema).forEach(async (key) => { 8 | if (schema.schema[key].type) { 9 | code.push(textFieldByType(key, schema.schema[key].type, schema.schema[key])); 10 | } else if (Array.isArray(schema.schema[key])) { 11 | console.warn('UNIMPLEMENTED SUB ARRAY ya array ', key); 12 | // if it's an array of strings, tags work great. 13 | code.push(textFieldByType(key, 'Array', schema.schema[key])); 14 | } else { 15 | Object.keys(schema.schema[key]).forEach((_key) => { 16 | code.push(textFieldByType(`${key}.${_key}`, schema.schema[key][_key].type, schema.schema[key][_key])); 17 | }); 18 | } 19 | }); 20 | return code.join('\n'); 21 | }; 22 | -------------------------------------------------------------------------------- /app/components/typeToValue.js: -------------------------------------------------------------------------------- 1 | module.exports = (type) => { 2 | let val = ""; 3 | switch (type) { 4 | case 'ObjectId': 5 | case 'String': 6 | break; 7 | case 'Boolean': 8 | val = false; 9 | break; 10 | case 'Number': 11 | val = 0; 12 | break; 13 | default: 14 | } 15 | return val; 16 | }; 17 | -------------------------------------------------------------------------------- /app/makeApp.js: -------------------------------------------------------------------------------- 1 | const mkdirp = require('mkdirp'); 2 | const fs = require('fs'); 3 | 4 | const makeStatics = require('./statics/makeStatics'); 5 | const makeSrc = require('./src/makeSrc'); 6 | const makePages = require('./pages/makePages'); 7 | const makeComponents = require("./components/makeComponents"); 8 | 9 | module.exports = async (args) => { 10 | if (process.env.NODE_ENV !== 'dev' && fs.existsSync(args.destination)) return console.log(`uh oh, looks like there's something already at ${args.destination}`); // eslint-disable-line 11 | mkdirp.sync(args.destination); 12 | mkdirp.sync(`${args.destination}/src`); 13 | mkdirp.sync(`${args.destination}/pages`); 14 | mkdirp.sync(`${args.destination}/components`); 15 | console.log('APP => Statics'); // eslint-disable-line 16 | await makeStatics(args.destination); 17 | await makeSrc(args.destination); 18 | await makePages(args); 19 | await makeComponents(args); 20 | }; 21 | -------------------------------------------------------------------------------- /app/pages/_app.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | module.exports = async (file) => { 4 | const fileName = `${file}/pages/_app.js`; 5 | const code = ` 6 | import React from 'react'; 7 | import App, { Container } from 'next/app'; 8 | import Head from 'next/head'; 9 | import { ThemeProvider } from '@material-ui/styles'; 10 | import CssBaseline from '@material-ui/core/CssBaseline'; 11 | import theme from '../src/theme'; 12 | 13 | class MyApp extends App { 14 | componentDidMount() { 15 | // Remove the server-side injected CSS. 16 | const jssStyles = document.querySelector('#jss-server-side'); 17 | if (jssStyles) { 18 | jssStyles.parentNode.removeChild(jssStyles); 19 | } 20 | } 21 | 22 | render() { 23 | const { Component, pageProps } = this.props; 24 | 25 | return ( 26 | 27 | 28 | My page 29 | 30 | 31 | {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */} 32 | 33 | 34 | 35 | 36 | ); 37 | } 38 | } 39 | 40 | export default MyApp; 41 | `; 42 | fs.writeFileSync(fileName, code); 43 | }; 44 | -------------------------------------------------------------------------------- /app/pages/_document.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').html; 3 | 4 | module.exports = async (file) => { 5 | const fileName = `${file}/pages/_document.js`; 6 | const code = ` 7 | import React from 'react'; 8 | import Document, { Head, Main, NextScript } from 'next/document'; 9 | import { ServerStyleSheets } from '@material-ui/styles'; 10 | import flush from 'styled-jsx/server'; 11 | import theme from '../src/theme'; 12 | 13 | class MyDocument extends Document { 14 | render() { 15 | return ( 16 | 17 | 18 | 19 | {/* Use minimum-scale=1 to enable GPU rasterization */} 20 | 24 | {/* PWA primary color */} 25 | 26 | 30 | 31 | 32 |
33 | 34 | 35 | 36 | ); 37 | } 38 | } 39 | 40 | MyDocument.getInitialProps = async ctx => { 41 | // Resolution order 42 | // 43 | // On the server: 44 | // 1. app.getInitialProps 45 | // 2. page.getInitialProps 46 | // 3. document.getInitialProps 47 | // 4. app.render 48 | // 5. page.render 49 | // 6. document.render 50 | // 51 | // On the server with error: 52 | // 1. document.getInitialProps 53 | // 2. app.render 54 | // 3. page.render 55 | // 4. document.render 56 | // 57 | // On the client 58 | // 1. app.getInitialProps 59 | // 2. page.getInitialProps 60 | // 3. app.render 61 | // 4. page.render 62 | 63 | // Render app and page and get the context of the page with collected side effects. 64 | const sheets = new ServerStyleSheets(); 65 | const originalRenderPage = ctx.renderPage; 66 | 67 | ctx.renderPage = () => 68 | originalRenderPage({ 69 | enhanceApp: App => props => sheets.collect(), 70 | }); 71 | 72 | const initialProps = await Document.getInitialProps(ctx); 73 | 74 | return { 75 | ...initialProps, 76 | // Styles fragment is rendered after the app and page rendering finish. 77 | styles: ( 78 | 79 | {sheets.getStyleElement()} 80 | {flush() || null} 81 | 82 | ), 83 | }; 84 | }; 85 | 86 | export default MyDocument; 87 | `; 88 | fs.writeFileSync(fileName, code); 89 | }; 90 | -------------------------------------------------------------------------------- /app/pages/about.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').html; 3 | 4 | module.exports = async (file) => { 5 | const fileName = `${file}/pages/about.js`; 6 | const code = ` 7 | import React from 'react'; 8 | import Container from '@material-ui/core/Container'; 9 | import Typography from '@material-ui/core/Typography'; 10 | import Box from '@material-ui/core/Box'; 11 | import MuiLink from '@material-ui/core/Link'; 12 | import ProTip from '../src/ProTip'; 13 | import Link from '../src/Link'; 14 | 15 | function MadeWithLove() { 16 | return ( 17 | 18 | {'Built with love by the '} 19 | 20 | Sugarkubes 21 | 22 | {' team.'} 23 | 24 | ); 25 | } 26 | 27 | export default function About() { 28 | return ( 29 | 30 | 31 | 32 | Sugar Generator About Page 33 | 34 | Go to the main page 35 | 36 | 37 | 38 | 39 | ); 40 | } 41 | `; 42 | fs.writeFileSync(fileName, code); 43 | }; 44 | -------------------------------------------------------------------------------- /app/pages/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { uppercase } = require('../../api/utils'); 3 | const beautify = require('js-beautify').html; 4 | 5 | const getImportStrings = (schema) => { 6 | const names = schema.name ? [uppercase(schema.name)] : schema.map((s) => uppercase(s.name)); 7 | return names.map((name) => { 8 | return `import Create${name}Form from '../components/${name}/Create${name}Form'; 9 | import ${uppercase(name)}Table from '../components/${name}/${uppercase(name)}Table';` 10 | }).join('\n'); 11 | }; 12 | 13 | const getComponentStrings = (schema) => { 14 | const names = schema.name ? [uppercase(schema.name)] : schema.map((s) => uppercase(s.name)); 15 | return names.map((name) => ` 16 |
17 | 18 | 19 | 20 | Create ${uppercase(name)} Form 21 | 22 | 23 | 24 | <${uppercase(name)}Table /> 25 |
26 | `).join('\n'); 27 | }; 28 | 29 | module.exports = async ({ destination, schema }) => { 30 | schema = require(schema); // eslint-disable-line 31 | const fileName = `${destination}/pages/index.js`; 32 | const code = ` 33 | import React, { Component } from 'react'; 34 | import Container from '@material-ui/core/Container'; 35 | import Typography from '@material-ui/core/Typography'; 36 | import Box from '@material-ui/core/Box'; 37 | import Paper from '@material-ui/core/Paper'; 38 | import Divider from '@material-ui/core/Divider'; 39 | import MuiLink from '@material-ui/core/Link'; 40 | import Grid from '@material-ui/core/Grid'; 41 | import { makeStyles } from '@material-ui/core/styles'; 42 | ${getImportStrings(schema)} 43 | import ProTip from '../src/ProTip'; 44 | import Link from '../src/Link'; 45 | 46 | const useStyles = makeStyles(theme => { 47 | return ({ 48 | root: { 49 | flexGrow: 1, 50 | }, 51 | paper: { 52 | padding: theme.spacing(2), 53 | textAlign: 'center', 54 | color: theme.palette.text.secondary, 55 | margin: theme.spacing(2), 56 | }, 57 | section: { 58 | // backgroundColor: theme.palette.primary.main, 59 | margin: theme.spacing(5), 60 | } 61 | }) 62 | }); 63 | 64 | export default function Index () { 65 | const styles = useStyles() 66 | return ( 67 | 68 | 69 | 70 | Sugar Generator Index Page 71 | 72 | 73 | Go to the about page 74 | 75 | 76 | 77 | 78 | Create Form Component(s) 79 | 80 | 81 | 82 | ${getComponentStrings(schema)} 83 | 84 | 85 | 86 | {'Built with love by the '} 87 | 88 | Sugarkubes 89 | 90 | {' team.'} 91 | 92 | 93 | 94 | ); 95 | } 96 | `; 97 | fs.writeFileSync(fileName, code); 98 | }; 99 | -------------------------------------------------------------------------------- /app/pages/makePages.js: -------------------------------------------------------------------------------- 1 | const _app = require('./_app'); 2 | const _document = require('./_document'); 3 | const about = require('./about'); 4 | const idx = require('./index'); 5 | 6 | 7 | module.exports = ({ destination, schema }) => { 8 | about(destination); 9 | _document(destination); 10 | _app(destination); 11 | idx({ destination, schema }); 12 | }; 13 | -------------------------------------------------------------------------------- /app/src/Link.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | module.exports = async (file) => { 4 | const fileName = `${file}/src/Link.js`; 5 | const code = ` 6 | /* eslint-disable jsx-a11y/anchor-has-content */ 7 | import React from 'react'; 8 | import PropTypes from 'prop-types'; 9 | import clsx from 'clsx'; 10 | import { withRouter } from 'next/router'; 11 | import NextLink from 'next/link'; 12 | import MuiLink from '@material-ui/core/Link'; 13 | 14 | const NextComposed = React.forwardRef(function NextComposed(props, ref) { 15 | const { as, href, prefetch, ...other } = props; 16 | 17 | return ( 18 | 19 | 20 | 21 | ); 22 | }); 23 | 24 | NextComposed.propTypes = { 25 | as: PropTypes.string, 26 | href: PropTypes.string, 27 | prefetch: PropTypes.bool, 28 | }; 29 | 30 | // A styled version of the Next.js Link component: 31 | // https://nextjs.org/docs/#with-link 32 | function Link(props) { 33 | const { activeClassName, router, className: classNameProps, innerRef, naked, ...other } = props; 34 | 35 | const className = clsx(classNameProps, { 36 | [activeClassName]: router.pathname === props.href && activeClassName, 37 | }); 38 | 39 | if (naked) { 40 | return ; 41 | } 42 | 43 | return ; 44 | } 45 | 46 | Link.propTypes = { 47 | activeClassName: PropTypes.string, 48 | as: PropTypes.string, 49 | className: PropTypes.string, 50 | href: PropTypes.string, 51 | innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), 52 | naked: PropTypes.bool, 53 | onClick: PropTypes.func, 54 | prefetch: PropTypes.bool, 55 | router: PropTypes.shape({ 56 | pathname: PropTypes.string.isRequired, 57 | }).isRequired, 58 | }; 59 | 60 | Link.defaultProps = { 61 | activeClassName: 'active', 62 | }; 63 | 64 | const RouterLink = withRouter(Link); 65 | 66 | export default React.forwardRef((props, ref) => ); 67 | `; 68 | fs.writeFileSync(fileName, code); 69 | }; 70 | -------------------------------------------------------------------------------- /app/src/ProTip.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | module.exports = async (file) => { 4 | const fileName = `${file}/src/ProTip.js` 5 | const code = ` 6 | import React from 'react'; 7 | import { makeStyles } from '@material-ui/core/styles'; 8 | import Link from '@material-ui/core/Link'; 9 | import SvgIcon from '@material-ui/core/SvgIcon'; 10 | import Typography from '@material-ui/core/Typography'; 11 | 12 | function LightBulbIcon(props) { 13 | return ( 14 | 15 | 16 | 17 | ); 18 | } 19 | 20 | const useStyles = makeStyles(theme => ({ 21 | root: { 22 | margin: theme.spacing(6, 0, 3), 23 | }, 24 | lightBulb: { 25 | verticalAlign: 'middle', 26 | marginRight: theme.spacing(1), 27 | }, 28 | })); 29 | 30 | export default function ProTip() { 31 | const classes = useStyles(); 32 | return ( 33 | 34 | 35 | Pro tip: See more{' '} 36 | 37 | page layout examples 38 | {' '} 39 | on the Material-UI documentation. 40 | 41 | 42 | ); 43 | } 44 | `; 45 | fs.writeFileSync(fileName, code); 46 | }; 47 | -------------------------------------------------------------------------------- /app/src/apiCall.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | module.exports = async (file) => { 4 | const fileName = `${file}/src/apiCall.js`; 5 | const code = ` 6 | import fetch from 'isomorphic-unfetch'; 7 | const queryString = require('query-string'); 8 | 9 | import { apiURL } from '../config'; 10 | 11 | module.exports = ({ url, method, data, params = null }) => { 12 | // Default options are marked with * 13 | let stringified = ""; 14 | if (params) { 15 | Object.keys(params).forEach((param) => { 16 | if (param === 'orderBy' && params[param]) { 17 | const orderBy = {}; 18 | orderBy[params[param].field] = params.orderDirection; 19 | delete params.orderDirection; 20 | delete params.orderBy; 21 | params.sort = JSON.stringify(orderBy); 22 | } 23 | if (typeof params[param] === 'object') params[param] = JSON.stringify(params[param]); 24 | }) 25 | stringified = \`?\${queryString.stringify(params)}\`; 26 | } 27 | const fetchObject = { 28 | headers: { 29 | 'Content-Type': 'application/json', 30 | // 'Content-Type': 'application/x-www-form-urlencoded', 31 | // Authorization? 32 | }, 33 | // credentials: 'include', // include, *same-origin, omit 34 | cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached 35 | mode: 'cors', // no-cors, cors, *same-origin 36 | method: 'GET', // *GET, POST, PUT, DELETE, etc. 37 | redirect: 'follow', // manual, *follow, error 38 | referrer: 'no-referrer', // no-referrer, *client 39 | } 40 | if (method) fetchObject.method = method; 41 | if (data) fetchObject.body = JSON.stringify(data); 42 | 43 | return fetch(\`\${apiURL}/\${url}\${stringified}\`, fetchObject) 44 | .then(response => response.json()); 45 | } 46 | `; 47 | fs.writeFileSync(fileName, code); 48 | }; 49 | -------------------------------------------------------------------------------- /app/src/makeSrc.js: -------------------------------------------------------------------------------- 1 | const link = require('./Link'); 2 | const pro = require('./ProTip'); 3 | const theme = require('./theme'); 4 | const apiCall = require('./apiCall'); 5 | 6 | module.exports = (dest) => { 7 | theme(dest); 8 | pro(dest); 9 | link(dest); 10 | apiCall(dest); 11 | }; 12 | -------------------------------------------------------------------------------- /app/src/theme.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | 4 | module.exports = async (file) => { 5 | const fileName = `${file}/src/theme.js` 6 | 7 | const code = ` 8 | import { createMuiTheme } from '@material-ui/core/styles'; 9 | import { red } from '@material-ui/core/colors'; 10 | 11 | // Create a theme instance. 12 | const theme = createMuiTheme({ 13 | palette: { 14 | primary: { 15 | main: '#556cd6', 16 | }, 17 | secondary: { 18 | main: '#19857b', 19 | }, 20 | error: { 21 | main: red.A400, 22 | }, 23 | background: { 24 | default: '#fff', 25 | }, 26 | }, 27 | }); 28 | 29 | export default theme; 30 | `; 31 | const pretty = beautify(code, { indent_size: 2, space_in_empty_paren: true }); 32 | fs.writeFileSync(fileName, pretty); 33 | }; 34 | -------------------------------------------------------------------------------- /app/statics/babelrc.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | 4 | module.exports = async (fileName) => { 5 | fileName = `${fileName}/.babelrc`; 6 | const code = ` 7 | { 8 | "presets": ["next/babel"] 9 | } 10 | `; 11 | const pretty = beautify(code, { indent_size: 2, space_in_empty_paren: true }); 12 | fs.writeFileSync(fileName, pretty); 13 | }; 14 | -------------------------------------------------------------------------------- /app/statics/config.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | 4 | module.exports = async (fileName) => { 5 | fileName = `${fileName}/config.json`; 6 | const code = ` 7 | { 8 | "apiURL": "http://localhost:7777" 9 | } 10 | `; 11 | const pretty = beautify(code, { indent_size: 2, space_in_empty_paren: true }); 12 | fs.writeFileSync(fileName, pretty); 13 | }; 14 | -------------------------------------------------------------------------------- /app/statics/dockerfile.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | 4 | module.exports = async (fileName) => { 5 | fileName = `${fileName}/Dockerfile`; 6 | const code = `FROM node:alpine 7 | RUN mkdir -p /usr/src/app 8 | WORKDIR /usr/src/app 9 | COPY . /usr/src/app 10 | RUN npm install --production 11 | RUN npm run build 12 | ENV NODE_ENV production 13 | ENV PORT 3000 14 | EXPOSE 3000 15 | CMD [ "npm", "run", "start-prod" ] 16 | `; 17 | fs.writeFileSync(fileName, code); 18 | }; 19 | -------------------------------------------------------------------------------- /app/statics/gitignore.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | 4 | module.exports = async (fileName) => { 5 | fileName = `${fileName}/.gitignore`; 6 | const code = `# See https://help.github.com/ignore-files/ for more about ignoring files. 7 | # dependencies 8 | /node_modules 9 | 10 | # misc 11 | .DS_Store 12 | .env.local 13 | .env.development.local 14 | .env.test.local 15 | .env.production.local 16 | 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* 20 | 21 | # Next.js 22 | /.next`; 23 | const pretty = beautify(code, { indent_size: 2, space_in_empty_paren: true }); 24 | fs.writeFileSync(fileName, pretty); 25 | }; 26 | -------------------------------------------------------------------------------- /app/statics/makeStatics.js: -------------------------------------------------------------------------------- 1 | const babel = require('./babelrc'); 2 | const git = require('./gitignore'); 3 | const next = require('./next-config'); 4 | const pack = require('./package'); 5 | const readme = require('./readme'); 6 | const config = require('./config'); 7 | const dockerfile = require('./dockerfile'); 8 | 9 | module.exports = async (dest) => { 10 | babel(dest); 11 | git(dest); 12 | next(dest); 13 | pack(dest); 14 | readme(dest); 15 | config(dest); 16 | dockerfile(dest); 17 | }; 18 | -------------------------------------------------------------------------------- /app/statics/next-config.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | 4 | module.exports = async (fileName) => { 5 | fileName = `${fileName}/next.config.js`; 6 | 7 | const code = `module.exports = {};`; 8 | const pretty = beautify(code, { indent_size: 2, space_in_empty_paren: true }); 9 | if (!fs.existsSync(fileName)) fs.writeFileSync(fileName, pretty); 10 | }; 11 | -------------------------------------------------------------------------------- /app/statics/package.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | 4 | module.exports = async (fileName) => { 5 | fileName = `${fileName}/package.json`; 6 | const code = ` 7 | { 8 | "name": "app", 9 | "version": "1.0.0", 10 | "description": "", 11 | "main": "index.js", 12 | "scripts": { 13 | "dev": "next", 14 | "build": "next build", 15 | "start": "next start", 16 | "start-prod": "next start -p \${PORT}" 17 | }, 18 | "author": "", 19 | "license": "", 20 | "dependencies": { 21 | "@material-ui/core": "^4.0.2", 22 | "@material-ui/icons": "^4.0.1", 23 | "@material-ui/styles": "^4.0.2", 24 | "clsx": "^1.0.4", 25 | "isomorphic-unfetch": "^3.0.0", 26 | "material-table": "^1.39.0", 27 | "next": "^8.1.0", 28 | "prop-types": "^15.7.2", 29 | "query-string": "^6.7.0", 30 | "react": "^16.8.6", 31 | "react-dom": "^16.8.6" 32 | } 33 | } 34 | `; 35 | const pretty = beautify(code, { indent_size: 2, space_in_empty_paren: true }); 36 | fs.writeFileSync(fileName, pretty); 37 | }; 38 | -------------------------------------------------------------------------------- /app/statics/readme.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | 4 | module.exports = async (fileName) => { 5 | fileName = `${fileName}/README.md`; 6 | const code = ` 7 | # Next.js with material ui example 8 | 9 | ## How to use 10 | 11 | Download the example [or clone the repo](https://github.com/mui-org/material-ui): 12 | 13 | \`\`\`sh 14 | curl https://codeload.github.com/mui-org/material-ui/tar.gz/master | tar -xz --strip=2 material-ui-master/examples/nextjs 15 | cd nextjs 16 | \`\`\` 17 | 18 | Install it and run: 19 | 20 | \`\`\`sh 21 | npm install 22 | npm run dev 23 | \`\`\` 24 | 25 | ## The idea behind the example 26 | 27 | [Next.js](https://github.com/zeit/next.js) is a framework for server-rendered React apps. 28 | [Hooks](https://reactjs.org/docs/hooks-state.html) are an upcoming feature of React. 29 | `; 30 | const pretty = beautify(code, { indent_size: 2, space_in_empty_paren: true }); 31 | fs.writeFileSync(fileName, pretty); 32 | }; 33 | -------------------------------------------------------------------------------- /embeddable/createHTMLCreateForm.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').html; 3 | const { uppercase } = require('../api/utils'); 4 | const uuid = require('uuid').v4; 5 | module.exports = async ({ schema, destination }) => { 6 | const getReactState = require('../app/components/getReactState'); 7 | const textFieldsByType = require('../app/components/textFieldsByType'); 8 | const componentID = uuid(); 9 | const fileName = `${destination}/${uppercase(schema.name)}/${uppercase(schema.name)}CreateForm.html`; 10 | const componentName = `${uppercase(schema.name)}CreateForm` 11 | 12 | const host = "http://localhost:8080" 13 | const code = [ 14 | ` 15 | 16 | 17 | 18 | ${componentName} 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
36 | 294 | 295 | 296 | `]; 297 | 298 | const pretty = beautify(code.join("\n"), { indent_size: 2, space_in_empty_paren: true }); 299 | fs.writeFileSync(fileName, pretty); 300 | }; 301 | -------------------------------------------------------------------------------- /embeddable/createHTMLTable.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').html; 3 | const { uppercase } = require('../api/utils'); 4 | const uuid = require('uuid').v4; 5 | module.exports = async ({ schema, destination }) => { 6 | const getReactState = require('../app/components/getReactState'); 7 | const textFieldsByType = require('../app/components/textFieldsByType'); 8 | const getTableColumns = require('../app/components/getTableColumns'); 9 | 10 | const componentID = uuid(); 11 | const fileName = `${destination}/${uppercase(schema.name)}/${uppercase(schema.name)}Table.html`; 12 | const componentName = `${uppercase(schema.name)}Table` 13 | console.log('FILENAME', fileName) 14 | 15 | const host = "http://localhost:8080" 16 | const code = [ 17 | ` 18 | 19 | 20 | 21 | ${componentName} 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
43 | 357 | 358 | 359 | `]; 360 | 361 | const pretty = beautify(code.join("\n"), { indent_size: 2, space_in_empty_paren: true }); 362 | fs.writeFileSync(fileName, pretty); 363 | }; 364 | -------------------------------------------------------------------------------- /embeddable/createJSCreateForm.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | const { uppercase } = require('../api/utils'); 4 | const uuid = require('uuid').v4; 5 | module.exports = async ({ schema, destination }) => { 6 | const fileName = `${destination}/${uppercase(schema.name)}/${uppercase(schema.name)}CreateForm.js`; 7 | const componentName = `${uppercase(schema.name)}CreateForm`; 8 | 9 | const host = "http://localhost:8080"; 10 | console.log('what to do about HOST', host) 11 | const code = [` 12 | window.${componentName} = zoid.create({ 13 | // The html tag used to render my component 14 | tag: '${schema.name}-create-form', 15 | // The url that will be loaded in the iframe or popup, when someone includes my component on their page 16 | url: '${host}/${componentName}.html', 17 | autoResize: { 18 | height: true, 19 | width: false, 20 | }, 21 | dimensions: { 22 | width: '100%', 23 | height: '100%' 24 | }, 25 | attributes: { 26 | iframe: { 27 | scrolling: "no" 28 | } 29 | }, 30 | }); 31 | 32 | `]; 33 | 34 | const pretty = beautify(code.join("\n"), { indent_size: 2, space_in_empty_paren: true }); 35 | fs.writeFileSync(fileName, pretty); 36 | }; 37 | -------------------------------------------------------------------------------- /embeddable/createJSTable.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const beautify = require('js-beautify').js; 3 | const { uppercase } = require('../api/utils'); 4 | const uuid = require('uuid').v4; 5 | module.exports = async ({ schema, destination }) => { 6 | const fileName = `${destination}/${uppercase(schema.name)}/${uppercase(schema.name)}Table.js`; 7 | const componentName = `${uppercase(schema.name)}Table`; 8 | 9 | const host = "http://localhost:8080"; 10 | console.log('what to do about HOST', host) 11 | const code = [` 12 | window.${componentName} = zoid.create({ 13 | // The html tag used to render my component 14 | tag: '${schema.name}-table', 15 | // The url that will be loaded in the iframe or popup, when someone includes my component on their page 16 | url: '${host}/${componentName}.html', 17 | autoResize: { 18 | height: true, 19 | width: false, 20 | }, 21 | dimensions: { 22 | width: '100%', 23 | height: '100%' 24 | }, 25 | attributes: { 26 | iframe: { 27 | scrolling: "no" 28 | } 29 | }, 30 | }); 31 | 32 | `]; 33 | 34 | const pretty = beautify(code.join("\n"), { indent_size: 2, space_in_empty_paren: true }); 35 | fs.writeFileSync(fileName, pretty); 36 | }; 37 | -------------------------------------------------------------------------------- /embeddable/makeEmbeddable.js: -------------------------------------------------------------------------------- 1 | const mkdirp = require('mkdirp'); 2 | const fs = require('fs'); 3 | 4 | // const makeStatics = require('./statics/makeStatics'); 5 | // const makeSrc = require('./src/makeSrc'); 6 | // const makePages = require('./pages/makePages'); 7 | // const makeComponents = require("./components/makeComponents"); 8 | const createHTMLCreateForm = require('./createHTMLCreateForm'); 9 | const createJSCreateForm = require('./createJSCreateForm'); 10 | const createJSTable = require('./createJSTable'); 11 | const createHTMLTable = require('./createHTMLTable'); 12 | 13 | const { uppercase } = require('../api/utils'); 14 | 15 | const embeddableSet = ({ schema, destination }) => { 16 | createHTMLCreateForm({ destination, schema }); 17 | createJSCreateForm({ destination, schema }); 18 | createJSTable({ destination, schema }); 19 | 20 | // Table isnt working. Can't seem to find the export for material-table 21 | // createHTMLTable({ destination, schema }); 22 | } 23 | 24 | module.exports = async ({ schema, destination }) => { 25 | if (process.env.NODE_ENV !== 'dev' && fs.existsSync(args.destination)) return console.log(`uh oh, looks like there's something already at ${args.destination}`); // eslint-disable-line 26 | mkdirp.sync(destination); 27 | schema = require(schema); // eslint-disable-line 28 | if (Array.isArray(schema)) { 29 | schema.forEach((s) => { 30 | mkdirp.sync(`${destination}/${uppercase(s.name)}`); 31 | embeddableSet({ destination, schema: s }); 32 | // console.log('yes array', s); 33 | }); 34 | } else { 35 | embeddableSet({ destination, schema }); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /graphql-mutation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wrannaman/generators/6ca0107a171182c94eccaea412a5a574ca82b284/graphql-mutation.png -------------------------------------------------------------------------------- /graphql-schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wrannaman/generators/6ca0107a171182c94eccaea412a5a574ca82b284/graphql-schema.png -------------------------------------------------------------------------------- /graphql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wrannaman/generators/6ca0107a171182c94eccaea412a5a574ca82b284/graphql.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const path = require('path'); 3 | const { ArgumentParser } = require('argparse'); 4 | const { validateSchema } = require('./api/utils'); 5 | const makeAPI = require('./api/makeAPI'); 6 | const makeApp = require('./app/makeApp'); 7 | const makeEmbeddable = require('./embeddable/makeEmbeddable'); 8 | 9 | const parser = new ArgumentParser({ 10 | version: require('./package.json').version, 11 | addHelp: true, 12 | description: 'Sugar Generator', 13 | }); 14 | 15 | parser.addArgument( 16 | ['-t', '--type'], 17 | { 18 | help: 'one of ["api", "component"]', 19 | choices: ["api", "component"], 20 | metavar: "Generator Type", 21 | dest: "type", 22 | defaultValue: "api", 23 | required: false, 24 | } 25 | ); 26 | 27 | parser.addArgument( 28 | ['-f', '--flavor'], 29 | { 30 | help: 'one of ["graphql", "rest"]', 31 | choices: ["rest", "graphql"], 32 | required: false, 33 | metavar: "flavor. do you want just rest or graphql + rest?", 34 | dest: "flavor", 35 | defaultValue: "graphql", 36 | } 37 | ); 38 | 39 | parser.addArgument( 40 | ['-l', '--log'], 41 | { 42 | help: 'logging', 43 | choices: [true, false], 44 | metavar: "logging", 45 | dest: "logging", 46 | defaultValue: true, 47 | } 48 | ); 49 | 50 | // required 51 | 52 | parser.addArgument( 53 | ['-s', '--schema'], 54 | { 55 | help: 'path to JSON of mongodb schema', 56 | required: true, 57 | metavar: "Schema", 58 | dest: "schema" 59 | } 60 | ); 61 | 62 | parser.addArgument( 63 | ['-o', '--output'], 64 | { 65 | help: 'where will the generated code go?', 66 | metavar: "output", 67 | dest: "destination", 68 | // defaultValue: __dirname, 69 | } 70 | ); 71 | 72 | const args = parser.parseArgs(); 73 | console.log('ARGS', args) 74 | 75 | args.schema = path.join(process.cwd(), args.schema); 76 | args.destination = path.join(process.cwd(), args.destination); 77 | 78 | const validSchema = validateSchema(args.schema); 79 | 80 | if (validSchema === true) { 81 | makeAPI(Object.assign({}, args, { destination: `${args.destination}/api` })); 82 | makeApp(Object.assign({}, args, { destination: `${args.destination}/app` })); 83 | // makeEmbeddable(Object.assign({}, args, { destination: `${args.destination}/embeddable` })); 84 | console.log('not making embeddable'); 85 | } else { 86 | console.error('Error => invalid schema.', validSchema === false ? '' : validSchema); 87 | } 88 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wrannaman/generators/6ca0107a171182c94eccaea412a5a574ca82b284/logo.png -------------------------------------------------------------------------------- /monkey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wrannaman/generators/6ca0107a171182c94eccaea412a5a574ca82b284/monkey.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sugar-generate", 3 | "version": "0.1.6", 4 | "description": "Auto generate OAS 3.0 REST + GraphQL APIs (Node + MongoDB)", 5 | "main": "./index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "patch-release": "npm publish && git push --follow-tags" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/sugarkubes/generators" 13 | }, 14 | "bin": { 15 | "sugar-generate": "./index.js" 16 | }, 17 | "keywords": [ 18 | "sugar", 19 | "sugarkubes", 20 | "generator", 21 | "api generator", 22 | "developer tools", 23 | "mongodb", 24 | "mongoose", 25 | "mongo", 26 | "swagger", 27 | "swagger-generate", 28 | "generator", 29 | "yeoman", 30 | "yeoman-generator", 31 | "express", 32 | "swagger-ui", 33 | "openapi", 34 | "oas", 35 | "oasv3", 36 | "oas 3.0", 37 | "rest", 38 | "graphql", 39 | "apollo" 40 | ], 41 | "author": "wrannaman", 42 | "license": "MIT", 43 | "dependencies": { 44 | "argparse": "^1.0.10", 45 | "eslint-plugin-react": "^7.13.0", 46 | "js-beautify": "^1.9.0-beta5", 47 | "mkdirp": "^0.5.1", 48 | "moment": "^2.24.0", 49 | "uuid": "^3.3.2" 50 | }, 51 | "devDependencies": { 52 | "chai": "^4.2.0", 53 | "chai-http": "^4.3.0", 54 | "eslint": "^5.8.0", 55 | "eslint-config-airbnb": "^17.1.0", 56 | "eslint-plugin-import": "^2.14.0", 57 | "eslint-plugin-jsx-a11y": "^6.1.2", 58 | "mocha": "^6.1.4", 59 | "prettier": "^1.14.3", 60 | "prettier-eslint": "^8.8.2", 61 | "prettier-eslint-cli": "^4.7.1", 62 | "prettier-stylelint": "^0.4.2", 63 | "request": "^2.88.0" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /sample-schemas/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "schema": { 4 | "name": { 5 | "type": "String", 6 | "required": true 7 | }, 8 | "env_vars": { 9 | "type": "JSON", 10 | "default": {} 11 | }, 12 | "networks": [{ 13 | "type": "String", 14 | "trim": true, 15 | "min": [2, "value is too short"], 16 | "max": 100 17 | }], 18 | "project": { 19 | "type": "ObjectID", 20 | "ref": "Project", 21 | "required": true 22 | }, 23 | "status": { 24 | "type": "String", 25 | "enum": ["active", "inactive", "deleted"], 26 | "default": "active" 27 | }, 28 | "starting_ports": [{ 29 | "type": "Number", 30 | "trim": true, 31 | "min": [2, "value is too short"], 32 | "max": 100 33 | }], 34 | "docker_image": { 35 | "type": "String", 36 | "trim": true, 37 | "min": [2, "value is too short"], 38 | "max": 100 39 | }, 40 | "containers_per_device": { 41 | "type": "Number", 42 | "trim": true, 43 | "default": 1, 44 | "min": [2, "value is too short"], 45 | "max": 100 46 | }, 47 | "volumes": [{ 48 | "type": "String", 49 | "trim": true, 50 | "min": [2, "value is too short"], 51 | "max": 100 52 | }], 53 | "devices": [{ 54 | "type": "String", 55 | "trim": true, 56 | "min": [2, "value is too short"], 57 | "max": 100 58 | }], 59 | "privileged": { 60 | "type": "Bool", 61 | "default": false 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /sample-schemas/auth.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "user", 4 | "schema": { 5 | "name": { 6 | "type": "String", 7 | "default": "" 8 | }, 9 | "email": { 10 | "type": "String", 11 | "trim": true, 12 | "required": true, 13 | "unique": true, 14 | "immutable": true, 15 | "htmlType": "email" 16 | }, 17 | "phone": { 18 | "type": "Boolean", 19 | "default": false, 20 | "htmlType": "boolean" 21 | }, 22 | "token": { 23 | "type": "String", 24 | "unique": true 25 | }, 26 | "magic_link": { 27 | "type": "String", 28 | "default": "" 29 | }, 30 | "role": { 31 | "default": "user", 32 | "type": "String" 33 | }, 34 | "agree": { 35 | "type": "Boolean", 36 | "htmlType": "boolean" 37 | }, 38 | "last_login": { 39 | "type": "Number" 40 | }, 41 | "known_ips": [{ 42 | "type": "String" 43 | }], 44 | "blocked": { 45 | "type": "Boolean", 46 | "htmlType": "boolean", 47 | "default": false 48 | } 49 | }, 50 | "statics": { 51 | "statuses": ["created", "under_review", "listed", "deleted"], 52 | "status": { 53 | "active": "active", 54 | "inactive": "inactive", 55 | "deleted": "deleted" 56 | } 57 | } 58 | }, 59 | { 60 | "name": "configuration", 61 | "schema": { 62 | "jwt_expires_in_hours": { 63 | "type": "Number", 64 | "default": 24 65 | }, 66 | "allow_signup": { 67 | "type": "Boolean", 68 | "default": true 69 | }, 70 | "allowed_signin_methods": { 71 | "type": "String", 72 | "enum": ["email", "phone", "both"], 73 | "default": "email", 74 | "htmlType": "radio" 75 | }, 76 | "force_agree_to": { 77 | "type": "Boolean", 78 | "default": true 79 | }, 80 | "allow_impersonation": { 81 | "type": "Boolean", 82 | "default": true 83 | } 84 | } 85 | } 86 | ] 87 | -------------------------------------------------------------------------------- /sample-schemas/deployment.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deployment", 3 | "schema": { 4 | "uuid": { 5 | "type": "String", 6 | "required": true, 7 | "trim": true, 8 | "min": [2, "Name is too short"], 9 | "max": 100 10 | }, 11 | "appConfigurations": [{ 12 | "type": "Mixed" 13 | }], 14 | "tags": [{ "type": "String" }], 15 | "status": { 16 | "type": "String", 17 | "enum": ["ready", "pending", "processing", "active", "inactive", "deleted"], 18 | "default": "ready" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sample-schemas/device.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "device", 3 | "schema": { 4 | "name": { 5 | "type": "String", 6 | "trim": true, 7 | "min": [2, "Name is too short"], 8 | "max": 100 9 | }, 10 | "uuid": { 11 | "type": "String", 12 | "required": true, 13 | "trim": true, 14 | "min": [2, "Name is too short"], 15 | "max": 100 16 | }, 17 | "project": { 18 | "type": "String", 19 | "ref": "Project", 20 | "required": true 21 | }, 22 | "sshKeys": { 23 | "ssh": { 24 | "type": "String", 25 | "trim": true, 26 | "min": [2, "Name is too short"], 27 | "max": 100 28 | }, 29 | "sshRO": { 30 | "type": "String", 31 | "trim": true, 32 | "min": [2, "Name is too short"], 33 | "max": 100 34 | }, 35 | "web": { 36 | "type": "String", 37 | "trim": true, 38 | "min": [2, "Name is too short"], 39 | "max": 100 40 | }, 41 | "webRO": { 42 | "type": "String", 43 | "trim": true, 44 | "min": [2, "Name is too short"], 45 | "max": 100 46 | } 47 | }, 48 | "tags": [{ "type": "String" }], 49 | "status": { 50 | "type": "String", 51 | "enum": ["active", "inactive", "deleted"], 52 | "default": "active" 53 | }, 54 | "hostname": { 55 | "type": "String", 56 | "trim": true, 57 | "min": [2, "hostname is too short"], 58 | "max": 100 59 | }, 60 | "type": { 61 | "type": "String", 62 | "trim": true, 63 | "min": [2, "type is too short"], 64 | "max": 100 65 | }, 66 | "platform": { 67 | "type": "String", 68 | "trim": true, 69 | "min": [2, "platform is too short"], 70 | "max": 100 71 | }, 72 | "arch": { 73 | "type": "String", 74 | "trim": true, 75 | "min": [2, "arch is too short"], 76 | "max": 100 77 | }, 78 | "release": { 79 | "type": "String", 80 | "trim": true, 81 | "min": [2, "release is too short"], 82 | "max": 100 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /sample-schemas/monkey.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema": { 3 | "first_name": { 4 | "type": "String", 5 | "default": "" 6 | }, 7 | "last_name": { 8 | "type": "String", 9 | "default": "" 10 | }, 11 | "isDead": { 12 | "type": "Boolean", 13 | "default": false 14 | }, 15 | "age": { 16 | "type": "Number", 17 | "default": false 18 | } 19 | }, 20 | "statics": { 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /sample-schemas/multi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "user", 4 | "schema": { 5 | "name": { 6 | "type": "String", 7 | "default": "" 8 | }, 9 | "email": { 10 | "type": "String", 11 | "trim": true, 12 | "required": true, 13 | "unique": true, 14 | "immutable": true, 15 | "htmlType": "email" 16 | }, 17 | "intro": { 18 | "type": "Boolean", 19 | "default": false, 20 | "htmlType": "boolean" 21 | }, 22 | "sub": { 23 | "one": { 24 | "type": "String", 25 | "trim": true, 26 | "required": true 27 | }, 28 | "two": { 29 | "type": "String", 30 | "required": true 31 | }, 32 | "three": { 33 | "type": "Number", 34 | "min": 0, 35 | "max": 1000, 36 | "htmlType": "number" 37 | } 38 | }, 39 | "role": { 40 | "type": "String", 41 | "enum": ["user", "maker"], 42 | "default": "user", 43 | "htmlType": "radio" 44 | }, 45 | "carrot": { 46 | "type": "String", 47 | "enum": ["sweet", "ugly"], 48 | "default": "ugly", 49 | "htmlType": "radio" 50 | } 51 | }, 52 | "statics": { 53 | "statuses": ["created", "under_review", "listed", "deleted"], 54 | "status": { 55 | "active": "active", 56 | "inactive": "inactive", 57 | "deleted": "deleted" 58 | } 59 | } 60 | }, 61 | { 62 | "name": "team", 63 | "schema": { 64 | "name": { 65 | "type": "String", 66 | "default": "" 67 | }, 68 | "users": { 69 | "type": "ObjectId", 70 | "ref": "Users", 71 | "htmlType": "text" 72 | } 73 | } 74 | } 75 | ] 76 | -------------------------------------------------------------------------------- /sample-schemas/secret.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "secret", 3 | "schema": { 4 | "key": { 5 | "type": "String", 6 | "trim": true, 7 | "min": [2, "key is too short"], 8 | "max": 100 9 | }, 10 | "value": { 11 | "type": "String", 12 | "required": true, 13 | "trim": true, 14 | "min": [2, "value is too short"], 15 | "max": 100 16 | }, 17 | "project": { 18 | "type": "ObjectID", 19 | "ref": "Project", 20 | "required": true 21 | }, 22 | "status": { 23 | "type": "String", 24 | "enum": ["active", "inactive", "deleted"], 25 | "default": "active" 26 | }, 27 | "type": { 28 | "type": "String", 29 | "enum": ["docker", "kv"], 30 | "default": "docker" 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /sample-schemas/simple.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema": { 3 | "name": { 4 | "type": "String", 5 | "default": "" 6 | }, 7 | "alive": { 8 | "type": "Boolean", 9 | "default": false 10 | }, 11 | "age": { 12 | "type": "Number", 13 | "default": false 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sample-schemas/user-can.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "user", 4 | "schema": { 5 | "identifier": { 6 | "type": "String", 7 | "default": "" 8 | }, 9 | "permissions": { 10 | "type": "JSON", 11 | "unique": true, 12 | "immutable": true 13 | }, 14 | "intro": { 15 | "type": "Boolean", 16 | "default": false, 17 | "htmlType": "boolean" 18 | }, 19 | "sub": { 20 | "one": { 21 | "type": "String", 22 | "trim": true, 23 | "required": true 24 | }, 25 | "two": { 26 | "type": "String", 27 | "required": true 28 | }, 29 | "three": { 30 | "type": "Number", 31 | "min": 0, 32 | "max": 1000, 33 | "htmlType": "number" 34 | } 35 | }, 36 | "role": { 37 | "type": "String", 38 | "enum": [ 39 | "user", 40 | "maker" 41 | ], 42 | "default": "user", 43 | "htmlType": "radio" 44 | }, 45 | "carrot": { 46 | "type": "String", 47 | "enum": [ 48 | "sweet", 49 | "ugly" 50 | ], 51 | "default": "ugly", 52 | "htmlType": "radio" 53 | } 54 | }, 55 | "statics": { 56 | "statuses": [ 57 | "created", 58 | "under_review", 59 | "listed", 60 | "deleted" 61 | ], 62 | "status": { 63 | "active": "active", 64 | "inactive": "inactive", 65 | "deleted": "deleted" 66 | } 67 | } 68 | }, 69 | { 70 | "name": "configuration", 71 | "schema": { 72 | "name": { 73 | "type": "String", 74 | "default": "" 75 | }, 76 | "users": { 77 | "type": "ObjectId", 78 | "ref": "Users", 79 | "htmlType": "text" 80 | } 81 | } 82 | } 83 | ] 84 | -------------------------------------------------------------------------------- /sample-schemas/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema": { 3 | "first_name": { 4 | "type": "String", 5 | "default": "" 6 | }, 7 | "last_name": { 8 | "type": "String", 9 | "default": "" 10 | }, 11 | "email": { 12 | "type": "String", 13 | "trim": true, 14 | "required": true, 15 | "unique": true, 16 | "immutable": true 17 | }, 18 | "password": { 19 | "type": "String", 20 | "trim": true, 21 | "select": false, 22 | "immutable": true, 23 | "required": true 24 | }, 25 | "intro": { 26 | "type": "Boolean", 27 | "default": false 28 | }, 29 | "team": { 30 | "type": "ObjectId", 31 | "ref": "Team" 32 | }, 33 | "sub": { 34 | "one": { 35 | "type": "String", 36 | "trim": true, 37 | "required": true 38 | }, 39 | "two": { 40 | "type": "Number", 41 | "required": true 42 | } 43 | }, 44 | "role": { 45 | "type": "String", 46 | "enum": ["user", "maker"], 47 | "default": "user" 48 | } 49 | }, 50 | "statics": { 51 | "statuses": ["created", "under_review", "listed", "deleted"], 52 | "status": { 53 | "active": "active", 54 | "inactive": "inactive", 55 | "deleted": "deleted" 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wrannaman/generators/6ca0107a171182c94eccaea412a5a574ca82b284/start.png -------------------------------------------------------------------------------- /table-component.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wrannaman/generators/6ca0107a171182c94eccaea412a5a574ca82b284/table-component.png -------------------------------------------------------------------------------- /user-form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wrannaman/generators/6ca0107a171182c94eccaea412a5a574ca82b284/user-form.png --------------------------------------------------------------------------------