Compare commits

...

10 Commits

Author SHA1 Message Date
dcd3c0a95b fix api proxy, route urls and filters 2021-07-16 00:39:41 -06:00
9253a84d7e add router and View links 2021-07-16 00:03:24 -06:00
7514dc03da setup for vue routes 2021-07-15 00:00:08 -06:00
7b18b0842a working and no errors on front or back ends 2021-07-08 03:15:36 -05:00
9869125965 working frontend build 2021-07-07 17:12:04 -05:00
be789ae882 add frontend frame and service logic 2021-07-03 00:41:03 -05:00
87312fbc86 create routes file creator
still needs some adjustments for GET requests
2021-01-27 01:41:45 -07:00
8e8c587c85 add funcParam builder with sorting by required 2021-01-23 15:11:14 -07:00
17deed412a add support for all crud and list in the service files 2021-01-23 01:14:04 -07:00
c176de3297 add support for searching queries 2021-01-22 23:18:15 -07:00
63 changed files with 11306 additions and 153 deletions

View File

@ -0,0 +1,31 @@
DEBUG=frame:*
OAUTH2_HOST=https://p01.bergx.io
WELL_KNOWN=https://p01.bergx.io/.well-known/openid-configuration
AUTHORIZATION_URL=https://p01.bergx.io/oauth/dialog/authorize
TOKEN_URL=https://p01.bergx.io/oauth/token
CLIENT_ID={{oauth-client-id}}
CLIENT_SECRET={{oauth-client-secret}}
# Update this to be your host name
CALLBACK_URL={{your-host-name}}/auth/callback
BX_CLIENT_ID={{bergx-client-id}}
BX_CLIENT_SECRET={{bergx-client-secret}}
# DB stuff
DB_HOST={{db-host}}
DB_PORT=3306
DB_USER={{db-user}}
DB_PASSWORD={db-user-password}}
DB_NAME={{db-name}}
# Session stuff
DOMAIN={{your-host-name}}
DB_HOST={{session-db-host}}
DB_PORT=3306
SESSION_DB_USER={{session-db-user}}
SESSION_DB_PASSWORD={{session-db-password}}
SESSION_DB_NAME={{session-db-name}}
SESSION_SECRET={{session-db-user-password

View File

@ -9,6 +9,7 @@
"dist": "yarn build && cp -R ./build ./dist/ && cp -R ./node_modules ./dist/"
},
"dependencies": {
"@bergx/bergx-sdk": "^1.0.1",
"@types/node": "^14.0.13",
"cookie-parser": "~1.4.4",
"cors": "^2.8.5",

View File

@ -1,4 +1,4 @@
import BergxSDK, { TryResponse } from 'bergx-sdk';
import BergxSDK, { TryResponse } from '@bergx/bergx-sdk';
import { userService } from '../users/userService';
import { DBUser } from '../users/usersMapper';

View File

@ -1,10 +1,10 @@
import * as express from 'express';
import * as passport from 'passport';
import { {{component}}Service } from './{{component}}Service';
var router = express.Router();
const debug = require('debug')('frame:{{component}}Routes');
// SYSTEM-BUILDER-{{component}}-routes
module.exports = router;

View File

@ -7,7 +7,6 @@ class {{Component}}Service {
public initialize() {
return Promise.resolve();
}
// SYSTEM-BUILDER-{{component}}-service
}

View File

@ -2,6 +2,34 @@
# yarn lockfile v1
"@bergx/bergx-sdk@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@bergx/bergx-sdk/-/bergx-sdk-1.0.1.tgz#c7097caf3233ddfecff2453216822b27f01c31d6"
integrity sha512-7xxB2g3TgBt0Wmf5CPkCnwHFCf+b0mQmJf5lMW3yi1StAZR/DcLxsRs/6TP/npg+x64VYaUeMIiKL6AqgRpMPg==
dependencies:
"@types/debug" "^4.1.5"
"@types/jsonwebtoken" "^8.3.8"
cross-fetch "^3.0.5"
debug "^4.1.1"
jsonwebtoken "^8.5.1"
"@types/debug@^4.1.5":
version "4.1.6"
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.6.tgz#0b7018723084918a865eff99249c490505df2163"
integrity sha512-7fDOJFA/x8B+sO1901BmHlf5dE1cxBU8mRXj8QOEDnn16hhGJv/IHxJtZhvsabZsIMn0eLIyeOKAeqSNJJYTpA==
"@types/jsonwebtoken@^8.3.8":
version "8.5.4"
resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.5.4.tgz#50ccaf0aa6f5d7b9956e70fe323b76e582991913"
integrity sha512-4L8msWK31oXwdtC81RmRBAULd0ShnAHjBuKT9MRQpjP0piNrZdXyTRcKY9/UIfhGeKIT4PvF5amOOUbbT/9Wpg==
dependencies:
"@types/node" "*"
"@types/node@*":
version "16.3.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.3.2.tgz#655432817f83b51ac869c2d51dd8305fb8342e16"
integrity sha512-jJs9ErFLP403I+hMLGnqDRWT0RYKSvArxuBVh2veudHV7ifEC1WAmjJADacZ7mRbA2nWgHtn8xyECMAot0SkAw==
"@types/node@^14.0.13":
version "14.0.13"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.13.tgz#ee1128e881b874c371374c1f72201893616417c9"
@ -58,6 +86,11 @@ bowser@2.9.0:
resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.9.0.tgz#3bed854233b419b9a7422d9ee3e85504373821c9"
integrity sha512-2ld76tuLBNFekRgmJfT2+3j5MIrP6bFict8WAIT3beq+srz1gcKNAdNKMqHqauQt63NmAa88HfP1/Ypa9Er3HA==
buffer-equal-constant-time@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=
bytes@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
@ -119,6 +152,13 @@ cors@^2.8.5:
object-assign "^4"
vary "^1"
cross-fetch@^3.0.5:
version "3.1.4"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39"
integrity sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==
dependencies:
node-fetch "2.6.1"
dasherize@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/dasherize/-/dasherize-2.0.0.tgz#6d809c9cd0cf7bb8952d80fc84fa13d47ddb1308"
@ -138,6 +178,13 @@ debug@4.1.1:
dependencies:
ms "^2.1.1"
debug@^4.1.1:
version "4.3.2"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
dependencies:
ms "2.1.2"
depd@2.0.0, depd@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
@ -168,6 +215,13 @@ dotenv@^8.2.0:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a"
integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==
ecdsa-sig-formatter@1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
dependencies:
safe-buffer "^5.0.1"
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@ -400,6 +454,74 @@ isarray@~1.0.0:
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
jsonwebtoken@^8.5.1:
version "8.5.1"
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d"
integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==
dependencies:
jws "^3.2.2"
lodash.includes "^4.3.0"
lodash.isboolean "^3.0.3"
lodash.isinteger "^4.0.4"
lodash.isnumber "^3.0.3"
lodash.isplainobject "^4.0.6"
lodash.isstring "^4.0.1"
lodash.once "^4.0.0"
ms "^2.1.1"
semver "^5.6.0"
jwa@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
dependencies:
buffer-equal-constant-time "1.0.1"
ecdsa-sig-formatter "1.0.11"
safe-buffer "^5.0.1"
jws@^3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
dependencies:
jwa "^1.4.1"
safe-buffer "^5.0.1"
lodash.includes@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=
lodash.isboolean@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=
lodash.isinteger@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=
lodash.isnumber@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=
lodash.isplainobject@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
lodash.isstring@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
lodash.once@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@ -448,7 +570,7 @@ ms@2.0.0:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
ms@^2.1.1:
ms@2.1.2, ms@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
@ -473,6 +595,11 @@ nocache@2.1.0:
resolved "https://registry.yarnpkg.com/nocache/-/nocache-2.1.0.tgz#120c9ffec43b5729b1d5de88cd71aa75a0ba491f"
integrity sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==
node-fetch@2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
oauth@0.9.x:
version "0.9.15"
resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
@ -618,11 +745,21 @@ safe-buffer@5.2.0:
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
safe-buffer@^5.0.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
"safer-buffer@>= 2.1.2 < 3":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
semver@^5.6.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
send@0.16.2:
version "0.16.2"
resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1"

23
frontend-frame/.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

24
frontend-frame/README.md Normal file
View File

@ -0,0 +1,24 @@
# frontend-frame
## Project setup
```
yarn install
```
### Compiles and hot-reloads for development
```
yarn serve
```
### Compiles and minifies for production
```
yarn build
```
### Lints and fixes files
```
yarn lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

View File

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

View File

@ -0,0 +1,51 @@
{
"name": "frontend-frame",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.6.5",
"register-service-worker": "^1.7.1",
"vue": "^2.6.11",
"vue-class-component": "^7.2.3",
"vue-property-decorator": "^9.1.2",
"vue-router": "^3.5.2"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^4.18.0",
"@typescript-eslint/parser": "^4.18.0",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-pwa": "~4.5.0",
"@vue/cli-plugin-typescript": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/eslint-config-typescript": "^7.0.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"typescript": "~4.1.5",
"vue-template-compiler": "^2.6.11"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended",
"@vue/typescript/recommended"
],
"parserOptions": {
"ecmaVersion": 2020
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.00251 14.9297L0 1.07422H6.14651L8.00251 4.27503L9.84583 1.07422H16L8.00251 14.9297Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 215 B

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

View File

@ -0,0 +1,2 @@
User-agent: *
Disallow:

View File

@ -0,0 +1,31 @@
<template>
<div id="app">
<!-- SYSTEM-BUILDER-list-components-html -->
<div class="routes-container">
<router-view/>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HelloWorld from './components/HelloWorld.vue';
// SYSTEM-BUILDER-list-components-imports
@Component
export default class App extends Vue {}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -0,0 +1,60 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript" target="_blank" rel="noopener">typescript</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa" target="_blank" rel="noopener">pwa</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component
export default class HelloWorld extends Vue {
@Prop() private msg!: string;
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

View File

@ -0,0 +1,48 @@
<template>
<div class="{{component}}-details">
<!-- SYSTEM-BUILDER-property-row -->
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { {{Component}}Config } from './{{component}}Types';
import { {{component}}Service } from './{{component}}Service';
@Component
export default class {{Component}}Editor extends Vue {
@Prop() private id!: string;
private {{component}}: {{Component}}Config = {
// SYSTEM-BUILDER-init-props
};
private loading: boolean = false;
private errorMessage: string = '';
private mounted() {
this.loadDetails();
}
private loadDetails() {
this.loading = true;
this.errorMessage = '';
{{component}}Service.get{{Component}}(this.id).then((res: {{Component}}Config) => {
this.loading = false;
this.errorMessage = '';
this.{{component}} = res;
}).catch(this.handleError.bind(this));
}
private handleError(err: any) {
// error
this.loading = false;
this.errorMessage = err.message;
}
}
</script>
<style scoped lang="css">
.errorMessage {
color: red;
}
</style>

View File

@ -0,0 +1,83 @@
<template>
<div class="{{component}}-editor">
<!-- SYSTEM-BUILDER-input-row -->
<div class="errorMessage" v-if="errorMessage != ''">{{errorMessage}}</div>
<div class="action-buttons">
<button class="btn" @click="close()">Cancel</button>
<button class="btn btn-secondary" :disabled="!isComplete" @click="isComplete && save{{Component}}()">Save</button>
</div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { {{component}}Service } from './{{component}}Service';
import { {{Component}}Config } from './{{component}}Types';
@Component
export default class {{Component}}Editor extends Vue {
@Prop() private {{component}}_id!: string;
private {{component}}: {{Component}}Config = {
// SYSTEM-BUILDER-init-props
};
private loading: boolean = false;
private errorMessage: string = '';
get isComplete() {
return true;
}
private close() {
this.$emit('close-editor');
}
private mounted() {
this.loadDetails();
}
private loadDetails() {
this.loading = true;
this.errorMessage = '';
{{component}}Service.get{{Component}}(this.{{component}}_id).then((res: {{Component}}Config) => {
this.loading = false;
this.errorMessage = '';
this.{{component}} = res;
}).catch(this.handleError.bind(this));
}
private save{{Component}}() {
this.loading = true;
this.errorMessage = '';
// either create or update...
if (this.{{component}}.{{component}}_id === '') {
{{component}}Service.create{{Component}}(this.{{component}}).then(() => {
// success
this.loading = false;
this.errorMessage = '';
this.close();
}).catch(this.handleError.bind(this));
} else {
{{component}}Service.update{{Component}}(this.{{component}}.{{component}}_id, this.{{component}}).then(() => {
// success
this.loading = false;
this.errorMessage = '';
this.close();
}).catch(this.handleError.bind(this));
}
}
private handleError(err: any) {
// error
this.loading = false;
this.errorMessage = err.message;
}
}
</script>
<style scoped lang="css">
.errorMessage {
color: red;
}
</style>

View File

@ -0,0 +1,61 @@
<template>
<div class="{{component}}-editor">
<div class="filter-row">
<!-- SYSTEM-BUILDER-filter-options -->
</div>
<div v-for="item in items" :key="item.{{component}}_id" class="input-row">
<div class="">{{ item.{{component}}_id }}</div>
<!-- SYSTEM-BUILDER-item-props -->
</div>
<div class="red-text" v-if="errorMessage !== ''">{{errorMessage}}</div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { {{component}}Service } from './{{component}}Service';
import { {{Component}}Config } from './{{component}}Types';
@Component
export default class {{Component}}Editor extends Vue {
private items: {{Component}}Config[] = [];
private loading: boolean = false;
private errorMessage: string = '';
private filterItems: {{Component}}Config = {
// SYSTEM-BUILDER-init-props
};
// SYSTEM-BUILDER-component-variables
// SYSTEM-BUILDER-component-functions
private mounted() {
this.get{{Components}}();
}
// TODO: add a search function if there is an endpoint with type 'search'
private handleError(err: any) {
// error
this.loading = false;
this.errorMessage = err.message;
}
}
</script>
<style scoped lang="css">
.red-text {
color: red;
}
.input-row {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.filter-row {
display: flex;
flex-direction: row;
justify-content: space-between;
}
</style>

View File

@ -0,0 +1,14 @@
import { del, get, post, put } from '@/services/fetch';
import { {{Component}}Config } from './{{component}}Types';
class {{Component}}Service {
// SYSTEM-BUILDER-service-variables
// SYSTEM-BUILDER-service-functions
}
let {{component}}Service = new {{Component}}Service();
export {
{{component}}Service,
{{Component}}Service
};

View File

@ -0,0 +1,13 @@
// SYSTEM-BUILDER-{{component}}-interfaces
// e.g.
interface {{Component}}Config {
{{component}}_id: string;
// SYSTEM-BUILDER-init-props
}
export {
{{Component}}Config,
// SYSTEM-BUILDER-{{component}}-exports
};

View File

@ -0,0 +1,11 @@
import Vue from 'vue'
import App from './App.vue'
import router from './router';
import './registerServiceWorker'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App),
}).$mount('#app')

View File

@ -0,0 +1,32 @@
/* eslint-disable no-console */
import { register } from 'register-service-worker'
if (process.env.NODE_ENV === 'production') {
register(`${process.env.BASE_URL}service-worker.js`, {
ready () {
console.log(
'App is being served from cache by a service worker.\n' +
'For more details, visit https://goo.gl/AFskqB'
)
},
registered () {
console.log('Service worker has been registered.')
},
cached () {
console.log('Content has been cached for offline use.')
},
updatefound () {
console.log('New content is downloading.')
},
updated () {
console.log('New content is available; please refresh.')
},
offline () {
console.log('No internet connection found. App is running in offline mode.')
},
error (error) {
console.error('Error during service worker registration:', error)
}
})
}

View File

@ -0,0 +1,12 @@
import Vue from 'vue';
import Router from 'vue-router';
Vue.use(Router);
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
// SYSTE-BUILDER-routes
]
});

View File

@ -0,0 +1,100 @@
declare var window: any;
const defaultBasePath = '';
export function fetchAsync(method: 'GET' | 'POST' | 'DELETE' | 'PUT', url: string, body?: any) {
const headers = { 'Content-Type': 'application/json; charset=utf-8' };
// if (access_token) headers['Authorization'] = `Token ${access_token}`;
return window.fetch(`${defaultBasePath}${url}`, {
method,
headers,
body: body && JSON.stringify(body)
}).then((response: any) => {
if (response.status === 401) {
window.location.href = '/auth/login';
// throw new Error('401');
}
if (response.status === 403 && url === '/api/v1/organization/') {
// this indicates a user that is not supposed to be in bx-console
window.location.href = '/auth/logout';
}
const result = response.json();
if (!response.ok) throw result;
return result;
});
}
export function get<T>(url: string): Promise<T> {
return fetchAsync('GET', url);
}
export function post<T>(url: string, body?: any): Promise<T> {
return fetchAsync('POST', url, body);
}
export function postWithFile<T>(url: string, body?: any): Promise<T> {
return fetchWithFile('POST', url, body);
}
export function putWithFile<T>(url: string, body?: any): Promise<T> {
return fetchWithFile('PUT', url, body);
}
export function fetchWithFile<T>(method: string, url: string, body?: any): Promise<T> {
return window.fetch(`${defaultBasePath}${url}`, {
method: method,
body: body
}).then((response: any) => {
if (response.status === 401) {
window.location.href = '/auth/login';
// throw new Error('401');
}
const result = response.json();
if (!response.ok) throw result;
return result;
})
}
export function del(url: string) {
return fetchAsync('DELETE', url);
}
export function put(url: string, body?: any) {
return fetchAsync('PUT', url, body);
}
export function toQueryString(obj: any) {
const parts = [];
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
parts.push(encodeURIComponent(i) + "=" + encodeURIComponent(obj[i]));
}
}
return parts.join("&");
}
export function serializeObject<T>(form: any) {
let obj: {[key: string]: any} = {};
if (typeof form == 'object' && form.nodeName == "FORM") {
for (let i = 0; i < form.elements.length; i++) {
const field = form.elements[i];
if (field.name
&& field.type != 'file'
&& field.type != 'reset'
&& field.type != 'submit'
&& field.type != 'button') {
if (field.type == 'select-multiple') {
obj[field.name] = '';
let tempvalue = '';
for (let j = 0; j < form.elements[i].options.length; j++) {
if (field.options[j].selected)
tempvalue += field.options[j].value + ';';
}
if (tempvalue.charAt(tempvalue.length - 1) === ';') obj[field.name] = tempvalue.substring(0, tempvalue.length - 1);
} else if ((field.type != 'checkbox' && field.type != 'radio') || field.checked) {
obj[field.name] = field.value;
}
}
}
}
return obj as T;
}

13
frontend-frame/src/shims-tsx.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
import Vue, { VNode } from 'vue'
declare global {
namespace JSX {
// tslint:disable no-empty-interface
interface Element extends VNode {}
// tslint:disable no-empty-interface
interface ElementClass extends Vue {}
interface IntrinsicElements {
[elem: string]: any
}
}
}

4
frontend-frame/src/shims-vue.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}

View File

@ -0,0 +1,40 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}

View File

@ -0,0 +1,10 @@
module.exports = {
devServer: {
proxy: {
'/api/': {
target: 'http://localhost:3000',
changeOrigin: true
},
}
}
}

9203
frontend-frame/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@
"pluralize": "^8.0.0"
},
"scripts": {
"start": "rm -rf dist && tsc && cp -r frame dist/ && cd dist && node index.js"
"start": "rm -rf dist && tsc && cp -r frame dist/ && cp -r frontend-frame dist/ && cd dist && node index.js"
},
"name": "system-builder",
"version": "1.0.0",

View File

@ -1,19 +1,13 @@
import * as path from 'path';
import * as fs from 'fs';
import {
SystemDef,
StorageDef,
EndpointDef,
Order,
Filter,
TableDef,
ColumnDef,
BelongsToDef,
ManyToManyDef
} from '../systemGenService';
let outDir = 'test';
export function createDatabase(storageDef: StorageDef) {
let tableCreationQueries: string[] = [];
let tableDeletionQueries: string[] = [];

View File

@ -123,7 +123,8 @@ let def: SystemDef = {
],
filters: [
{
param: 'list',
param: 'list_id',
type: 'string',
column: {
name: 'list_id',
table: 'task'
@ -133,6 +134,7 @@ let def: SystemDef = {
},
{
param: 'completed',
type: 'boolean',
column: {
name: 'completed',
table: 'task'
@ -142,6 +144,80 @@ let def: SystemDef = {
},
]
},
{
component: 'task',
table: 'task',
type: 'search',
columns: [
{
name: 'name',
table: 'task'
},
{
name: 'description',
table: 'task'
},
{
name: 'completed',
table: 'task'
},
{
name: 'completed_date',
table: 'task'
},
],
orderBy: [
{
column: {
name: 'modified',
table: 'task'
},
direction: 'desc'
}
],
filters: [
{
param: 'list_id',
type: 'string',
column: {
name: 'list_id',
table: 'task'
},
comparison: '=',
required: true
},
{
param: 'completed',
type: 'boolean',
column: {
name: 'completed',
table: 'task'
},
comparison: '=',
required: false
},
{
param: 'name',
type: 'string',
column: {
name: 'name',
table: 'task'
},
comparison: 'LIKE',
required: false
},
{
param: 'description',
type: 'string',
column: {
name: 'description',
table: 'task'
},
comparison: 'LIKE',
required: false
},
]
},
{
component: 'task',
table: 'task',
@ -175,7 +251,8 @@ let def: SystemDef = {
],
filters: [
{
param: 'list',
param: 'list_id',
type: 'string',
column: {
name: 'list_id',
table: 'task'
@ -185,6 +262,7 @@ let def: SystemDef = {
},
{
param: 'completed',
type: 'boolean',
column: {
name: 'completed',
table: 'task'
@ -203,23 +281,27 @@ let def: SystemDef = {
name: 'name',
table: 'task',
param: 'name',
type: 'string',
required: true
},
{
name: 'list_id',
table: 'task',
param: 'list',
type: 'string',
required: true
},
{
name: 'description',
table: 'task',
param: 'description'
param: 'description',
type: 'string'
},
{
name: 'completed',
table: 'task',
param: 'completed'
param: 'completed',
type: 'boolean',
}
],
},
@ -232,21 +314,25 @@ let def: SystemDef = {
name: 'name',
table: 'task',
param: 'name',
type: 'string'
},
{
name: 'description',
table: 'task',
param: 'description'
param: 'description',
type: 'string'
},
{
name: 'completed',
table: 'task',
param: 'completed'
param: 'completed',
type: 'boolean'
},
{
name: 'completed_date',
table: 'task',
param: 'completedDate'
param: 'completedDate',
type: 'string'
},
],
filters: [
@ -256,7 +342,8 @@ let def: SystemDef = {
table: 'task'
},
comparison: '=',
param: 'task',
param: 'task_id',
type: 'string',
required: true
},
]
@ -273,7 +360,8 @@ let def: SystemDef = {
table: 'task'
},
comparison: '=',
param: 'task',
param: 'task_id',
type: 'string',
required: true
},
]
@ -311,7 +399,8 @@ let def: SystemDef = {
table: 'task'
},
comparison: '=',
param: 'task',
param: 'task_id',
type: 'string',
required: true
},
]
@ -357,7 +446,8 @@ let def: SystemDef = {
],
filters: [
{
param: 'user',
param: 'user_id',
type: 'string',
column: {
name: 'user_id',
table: 'list_user'

View File

@ -0,0 +1,29 @@
import {ComponentDef, EndpointDef, SystemDef} from "../systemGenService";
import path from "path";
import fs from "fs";
export function buildAppLinks(component: ComponentDef, outDir: string, systemDef: SystemDef): Promise<void> {
let listEndpoints: EndpointDef[] = component.endpoints.filter((epd: EndpointDef) => {
return epd.type === 'list';
});
if (listEndpoints.length) {
return new Promise<void>((resolve) => {
const separator: string = `<!-- SYSTEM-BUILDER-list-components-html -->`;
let fileLocation = path.join(process.cwd(), `frontend-${systemDef.name}`, 'src', 'App.vue');
let initServiceFile: string = fs.readFileSync(fileLocation, 'utf8');
let parts = initServiceFile.split(separator);
parts[0] = parts[0] + createLinksMarkup(component, systemDef);
let newServiceFile = parts.join(separator);
fs.writeFileSync(fileLocation, newServiceFile, 'utf8');
resolve();
});
}
return Promise.resolve();
}
function createLinksMarkup(component: ComponentDef, systemDef: SystemDef): string {
let out = `<router-link to="/${component.component}">${component.component} List</router-link><br/>
`;
return out;
}

View File

@ -0,0 +1,61 @@
import {ColumnDef, ColumnRef, ComponentDef, EndpointDef, SystemDef, TableDef} from "../systemGenService";
import path from "path";
import fs from "fs";
import {createDetailsInitValues} from "../helpers";
export function buildDetailsView(component: ComponentDef, outDir: string, systemDef: SystemDef): Promise<void> {
let detailsEndpoints: EndpointDef[] = component.endpoints.filter((epd: EndpointDef) => {
return epd.type === 'item';
});
if (detailsEndpoints.length) {
return insertFEDetailsMarkup(detailsEndpoints[0], outDir).then(() => {
return insertFEDetailsCode(detailsEndpoints[0], outDir, systemDef);
});
} else {
return Promise.resolve();
}
}
function insertFEDetailsCode(view: EndpointDef, outDir: string, systemDef: SystemDef) {
return new Promise<void>((resolve) => {
const separator: string = `// SYSTEM-BUILDER-init-props`;
let fileLocation = path.join(outDir, `${view.component}Details.vue`)
let initServiceFile: string = fs.readFileSync(fileLocation, 'utf8');
let parts = initServiceFile.split(separator);
parts[0] = parts[0] + createDetailsInitValues(view, systemDef);
let newServiceFile = parts.join(separator);
fs.writeFileSync(fileLocation, newServiceFile, 'utf8');
resolve();
});
}
function insertFEDetailsMarkup(view: EndpointDef, outDir: string) {
return new Promise<void>((resolve) => {
const separator: string = `<!-- SYSTEM-BUILDER-property-row -->`;
let fileLocation = path.join(outDir, `${view.component}Details.vue`)
let initServiceFile: string = fs.readFileSync(fileLocation, 'utf8');
let parts = initServiceFile.split(separator);
parts[0] = parts[0] + createDetailsMarkup(view);
let newServiceFile = parts.join(separator);
fs.writeFileSync(fileLocation, newServiceFile, 'utf8');
resolve();
});
}
function createDetailsMarkup(view: EndpointDef): string {
let out = ``;
view.columns.forEach((column: ColumnRef) => {
out = out + `<div class="property-row">
<label>${column.name}: </label>
<div>{{ ${view.component}.${column.name} }}</div>
</div>
`;
});
return out;
}

View File

@ -0,0 +1,73 @@
import {ColumnDef, ColumnRef, ComponentDef, EndpointDef, SystemDef, TableDef} from "../systemGenService";
import path from "path";
import fs from "fs";
import {createDetailsInitValues, uppercaseFirstLetter} from "../helpers";
export function buildEditorView(component: ComponentDef, outDir: string, systemDef: SystemDef): Promise<void> {
let detailsEndpoints: EndpointDef[] = component.endpoints.filter((epd: EndpointDef) => {
return epd.type === 'item';
});
if (detailsEndpoints.length) {
return insertFEDetailsMarkup(detailsEndpoints[0], outDir).then(() => {
return insertFEDetailsCode(detailsEndpoints[0], outDir, systemDef);
});
} else {
return Promise.resolve();
}
}
function insertFEDetailsCode(view: EndpointDef, outDir: string, systemDef: SystemDef) {
return new Promise<void>((resolve) => {
const separator: string = `// SYSTEM-BUILDER-init-props`;
let fileLocation = path.join(outDir, `${view.component}Editor.vue`)
let initServiceFile: string = fs.readFileSync(fileLocation, 'utf8');
let parts = initServiceFile.split(separator);
parts[0] = parts[0] + createDetailsInitValues(view, systemDef);
let newServiceFile = parts.join(separator);
fs.writeFileSync(fileLocation, newServiceFile, 'utf8');
resolve();
});
}
function insertFEDetailsMarkup(view: EndpointDef, outDir: string) {
return new Promise<void>((resolve) => {
const separator: string = `<!-- SYSTEM-BUILDER-input-row -->`;
let fileLocation = path.join(outDir, `${view.component}Editor.vue`)
let initServiceFile: string = fs.readFileSync(fileLocation, 'utf8');
let parts = initServiceFile.split(separator);
parts[0] = parts[0] + createDetailsMarkup(view);
let newServiceFile = parts.join(separator);
fs.writeFileSync(fileLocation, newServiceFile, 'utf8');
resolve();
});
}
function createDetailsMarkup(view: EndpointDef): string {
let out = ``;
let inputType: {[key: string]: string} = {
"blob": "text",
"boolean": "checkbox",
"date": "date",
"dateTIME": "datetime-local",
"number": "number",
"string": "text",
};
view.columns.forEach((column: ColumnRef) => {
let inputTypeValue = 'text';
if (column.type) {
inputTypeValue = inputType[column.type];
}
out = out + `<div class="input-row">
<label>${uppercaseFirstLetter(view.component)} ${uppercaseFirstLetter(column.name)}: </label>
<input class="form-control" type="${inputTypeValue}" placeholder="${column.name}" v-model="${view.component}.${column.name}" />
</div>
`;
});
return out;
}

View File

@ -0,0 +1,166 @@
import {ComponentDef, EndpointDef, SystemDef} from "../systemGenService";
import path from "path";
import {
componentDestination,
initializeComponentFile,
removeTemplateFiles,
removeTemplateFolder,
uppercaseFirstLetter
} from "../helpers";
import fs from "fs";
import {buildServiceFunctionName, getFuncParams} from "../views/service-creator";
import {METHOD, URL} from "../views/routes-creator";
import {buildDetailsView} from "./details-view-builder";
import {buildListView} from "./list-view-builder";
import {buildEditorView} from "./editor-view-builder";
import {buildTypesView} from "./types-view-builder";
import {buildRoute} from "./route-view-builder";
import {buildAppLinks} from "./app-view-builder";
const ncp = require('ncp').ncp;
export function createFrontend(systemDef: SystemDef): Promise<void> {
let fePromises = [];
for (let i in systemDef.components) {
fePromises.push(createComponent(systemDef.components[i], systemDef).then(() => {
// build app router logic
return buildRoute(systemDef.components[i], componentDestination(systemDef.name, systemDef.components[i].component), systemDef);
}).then(() => {
// add a vue view and add it to App.vue
return buildAppLinks(systemDef.components[i], componentDestination(systemDef.name, systemDef.components[i].component), systemDef);
}).then(() => {
return removeTemplateFiles(componentDestination(systemDef.name, systemDef.components[i].component));
}));
}
// TODO: after all is done clean up the template files
return Promise.all(fePromises).then((res: void[]) => {
return removeTemplateFolder(componentDestination(systemDef.name, '{{component}}')).catch((err) => {
console.log(err);
});
});
}
function componentSource() {
return path.join(process.cwd(), 'frontend-frame', 'src', 'components', '{{component}}');
}
function createComponent(component: ComponentDef, systemDef: SystemDef) {
return new Promise<void>((componentResolve, componentReject) => {
ncp(componentSource(), componentDestination(systemDef.name, component.component), (err: any) => {
if (err) {
console.log(err);
componentReject();
return;
} else {
let componentDest: string = componentDestination(systemDef.name, component.component);
let servicePromise = initializeComponentFile(path.join(componentDest, '{{component}}Service.ets'), path.join(componentDest, `${component.component}Service.ts`), component.component, systemDef.name);
for (let i in component.endpoints) {
servicePromise = servicePromise.then(() => {
return insertFEServiceCode(component.endpoints[i], componentDest);
});
// TODO: use templates for Details, Editor, List and Types view based on the endpoints provided
// servicePromise = servicePromise.then(() => {
//
// return initializeComponentFile(path.join(componentDest, '{{component}}Service.ets'), path.join(componentDest, `${component.component}Service.ts`), component.component, systemDef.name);
// });
}
if (componentContains('list', component)) {
servicePromise = servicePromise.then(() => {
return initializeComponentFile(path.join(componentDest, '{{component}}List.vue'), path.join(componentDest, `${component.component}List.vue`), component.component, systemDef.name);
}).then(() => {
// TODO: build list contents based on the list endpoint
return buildListView(component, componentDest, systemDef);
});
}
if (componentContains('item', component)) {
servicePromise = servicePromise.then(() => {
return initializeComponentFile(path.join(componentDest, '{{component}}Details.vue'), path.join(componentDest, `${component.component}Details.vue`), component.component, systemDef.name);
}).then(() => {
// build list contents based on the item endpoint
return buildDetailsView(component, componentDest, systemDef);
});
}
if (componentContains('update', component) || componentContains('create', component)) {
servicePromise = servicePromise.then(() => {
return initializeComponentFile(path.join(componentDest, '{{component}}Editor.vue'), path.join(componentDest, `${component.component}Editor.vue`), component.component, systemDef.name);
}).then(() => {
// build list contents based on the item endpoint
return buildEditorView(component, componentDest, systemDef);
});
}
if (component.endpoints.length) {
// add a types file to power the whole component
servicePromise = servicePromise.then(() => {
return initializeComponentFile(path.join(componentDest, '{{component}}Types.ets'), path.join(componentDest, `${component.component}Types.ts`), component.component, systemDef.name);
}).then(() => {
return buildTypesView(component, componentDest, systemDef);
});
}
servicePromise.then(() => {
componentResolve();
});
}
});
});
}
function componentContains(type: string, component: ComponentDef) {
return component.endpoints.some((epd: EndpointDef) => {
return epd.type === type;
});
}
function insertFEServiceCode(view: EndpointDef, outDir: string) {
return new Promise<void>((resolve) => {
const separator: string = `// SYSTEM-BUILDER-service-functions`;
let fileLocation = path.join(outDir, `${view.component}Service.ts`)
let initServiceFile: string = fs.readFileSync(fileLocation, 'utf8');
let parts = initServiceFile.split(separator);
parts[0] = parts[0] + createFEServiceFunc(view);
let newServiceFile = parts.join(separator);
fs.writeFileSync(fileLocation, newServiceFile, 'utf8');
resolve();
});
}
function createFEServiceFunc(view: EndpointDef): string {
let func: string = '';
let isListOrSearch: boolean = view.type === 'list' || view.type === 'search';
let funcName: string = buildServiceFunctionName(view);
let funcParams: string[] = getFuncParams(view, true);
let columnParams: string[] = view.columns?.filter(column => column.param).map(column => { return '\n ' + column.param + ': ' + column.param; });
let filterParams: string[] = view.filters?.filter(filter => filter.param).map(filter => { return '\n ' + filter.param + ': ' + filter.param; }) || [];
if (isListOrSearch) {
funcParams.push('offset: number');
funcParams.push('limit: number');
filterParams.push('\n offset: offset');
filterParams.push('\n limit: limit');
}
let isGetOrDelete = view.type === 'item' || view.type === 'delete';
let method = METHOD[view.type];
if (method === 'delete') {
method = 'del';
}
let addTypeDeclaration = method === 'get' || method === 'post';
let useList = view.type === 'list' || view.type === 'search';
let url = view.component + '/' + URL[view.type](view).replace(':' + view.component + '_id', '\' + ' + view.component + '_id');
let isUpdateOrCreate = view.type === 'update' || view.type === 'create';
func = `
public ${funcName}(${view.type === 'update' ? view.component + '_id: string, ': ''}${isUpdateOrCreate ? 'params: ' + uppercaseFirstLetter(view.component) + 'Config' : funcParams.join(', ')}) {`;
if (!isGetOrDelete && !isUpdateOrCreate) {
func = func + `
let params = {${columnParams}${columnParams.length ? ',' : ''}${filterParams}
};`;
}
func = func + `
return ${method}${addTypeDeclaration ? '<' + uppercaseFirstLetter(view.component) + 'Config' + (useList ? '[]' : '') + '>': ''}('/api/v1/${url}${isGetOrDelete ? '' : (view.type === 'update' ? ', params' : '\', params')});
}
`;
return func;
}

View File

@ -0,0 +1,228 @@
import {ColumnDef, ColumnRef, ComponentDef, EndpointDef, Filter, SystemDef, TableDef} from "../systemGenService";
import path from "path";
import fs from "fs";
import {createDetailsInitValues, findColumn, makeSet, uppercaseFirstLetter} from "../helpers";
import pluralize from "pluralize";
import {getFuncParams} from "../views/service-creator";
const INIT_VALUE = {
"blob": "''",
"boolean": "false",
"date": "''",
"dateTIME": "''",
"number": "0",
"string": "''"
};
export function buildListView(component: ComponentDef, outDir: string, systemDef: SystemDef): Promise<void> {
let hasDeleteEndpoint = false;
let hasItemEndpoint = false;
let hasUpdateEndpoint = false;
let hasCreateEndpoint = false;
let listEndpoints: EndpointDef[] = component.endpoints.filter((epd: EndpointDef) => {
if (epd.type === 'delete') {
hasDeleteEndpoint = true;
}
if (epd.type === 'create') {
hasCreateEndpoint = true;
}
if (epd.type === 'update') {
hasUpdateEndpoint = true;
}
if (epd.type === 'item') {
hasItemEndpoint = true;
}
return epd.type === 'list' || epd.type === 'search';
});
let filterParams: Filter[] = [];
let endpointPromise = Promise.resolve();
for (let i in listEndpoints) {
if (i === '0') {
insertFEPropsCode(listEndpoints[i], outDir, systemDef);
}
if (listEndpoints[i].filters) {
filterParams = [...filterParams, ...listEndpoints[i].filters as Filter[]];
}
endpointPromise = endpointPromise.then(() => {
return insertFEListCode(listEndpoints[i], outDir, systemDef, hasDeleteEndpoint);
});
}
if (listEndpoints.length) {
endpointPromise = endpointPromise.then(() => {
return insertFEListMarkup(listEndpoints[0], outDir, hasDeleteEndpoint)
});
}
if (hasDeleteEndpoint && listEndpoints.length) {
endpointPromise = endpointPromise.then(() => {
return insertFEDeleteCode(listEndpoints[0], outDir);
});
}
// combine all duplicate filterParams
// @ts-ignore
let filterSet: Filter[] = makeSet<Filter>(filterParams, 'param');
// add filterParams to the markup as filter options
if (listEndpoints.length) {
endpointPromise = endpointPromise.then(() => {
return insertFEFiltersMarkup(listEndpoints[0], filterSet, outDir);
});
}
// TODO: add create component to sidebar
// TODO: add details component to sidebar
// TODO: if delete endpoint exists add delete button next to each row
return endpointPromise;
}
function insertFEListCode(view: EndpointDef, outDir: string, systemDef: SystemDef, hasDeleteEndpoint: boolean) {
return new Promise<void>((resolve) => {
const separator: string = `// SYSTEM-BUILDER-component-functions`;
let fileLocation = path.join(outDir, `${view.component}List.vue`)
let initServiceFile: string = fs.readFileSync(fileLocation, 'utf8');
let parts = initServiceFile.split(separator);
parts[0] = parts[0] + createListRequestFunc(view, systemDef, hasDeleteEndpoint);
let newServiceFile = parts.join(separator);
fs.writeFileSync(fileLocation, newServiceFile, 'utf8');
resolve();
});
}
function insertFEDeleteCode(view: EndpointDef, outDir: string) {
return new Promise<void>((resolve) => {
const separator: string = `// SYSTEM-BUILDER-component-functions`;
let fileLocation = path.join(outDir, `${view.component}List.vue`)
let initServiceFile: string = fs.readFileSync(fileLocation, 'utf8');
let parts = initServiceFile.split(separator);
parts[0] = parts[0] + createFEDeleteCode(view);
let newServiceFile = parts.join(separator);
fs.writeFileSync(fileLocation, newServiceFile, 'utf8');
resolve();
});
}
function insertFEListMarkup(view: EndpointDef, outDir: string, hasDeleteEndpoint: boolean) {
return new Promise<void>((resolve) => {
const separator: string = `<!-- SYSTEM-BUILDER-item-props -->`;
let fileLocation = path.join(outDir, `${view.component}List.vue`)
let initServiceFile: string = fs.readFileSync(fileLocation, 'utf8');
let parts = initServiceFile.split(separator);
parts[0] = parts[0] + createListMarkup(view, hasDeleteEndpoint);
let newServiceFile = parts.join(separator);
fs.writeFileSync(fileLocation, newServiceFile, 'utf8');
resolve();
});
}
function insertFEFiltersMarkup(view: EndpointDef, filters: Filter[], outDir: string) {
return new Promise<void>((resolve) => {
const separator: string = `<!-- SYSTEM-BUILDER-filter-options -->`;
let fileLocation = path.join(outDir, `${view.component}List.vue`)
let initServiceFile: string = fs.readFileSync(fileLocation, 'utf8');
let parts = initServiceFile.split(separator);
parts[0] = parts[0] + createFilterMarkup(view, filters);
let newServiceFile = parts.join(separator);
fs.writeFileSync(fileLocation, newServiceFile, 'utf8');
resolve();
});
}
function insertFEPropsCode(view: EndpointDef, outDir: string, systemDef: SystemDef) {
return new Promise<void>((resolve) => {
const separator: string = `// SYSTEM-BUILDER-init-props`;
let fileLocation = path.join(outDir, `${view.component}List.vue`)
let initServiceFile: string = fs.readFileSync(fileLocation, 'utf8');
let parts = initServiceFile.split(separator);
parts[0] = parts[0] + createDetailsInitValues(view, systemDef);
let newServiceFile = parts.join(separator);
fs.writeFileSync(fileLocation, newServiceFile, 'utf8');
resolve();
});
}
function createFilterMarkup(view: EndpointDef, filters: Filter[]): string {
let out = ``;
let inputType: {[key: string]: string} = {
"blob": "text",
"boolean": "checkbox",
"date": "date",
"dateTIME": "datetime-local",
"number": "number",
"string": "text",
};
filters.forEach((f: Filter) => {
if (f.type) {
// base the filter markup on type
out = out + `<div>
<label>${f.param}: </label>
<input type="${inputType[f.type] || 'text'}" placeholder="${f.param}" v-model="filterItems.${f.param}"/>
</div>
`;
} else {
// default to a string filter
out = out + `<div>
<input type="text" placeholder="${f.param}" v-model="filterItems.${f.param}"/>
</div>
`;
}
});
out = out + `<div>
<button @click="search${uppercaseFirstLetter(pluralize(view.component))}()">Search</button>
</div>
`;
return out;
}
function createListMarkup(view: EndpointDef, hasDeleteEndpoint: boolean): string {
let out = ``;
view.columns.forEach((column: ColumnRef) => {
out = out + `<div class="">{{ item.${column.name} }}</div>
`;
});
if (hasDeleteEndpoint) {
out = out + `<div class="red-text" @click="deleteItem(item.${view.component}_id)">delete</div>
`;
}
return out;
}
function createListRequestFunc(view: EndpointDef, systemDef: SystemDef, hasDeleteEndpoint: boolean) {
let isSearch: boolean = view.type === 'search';
let params: string[] = getFuncParams(view);
params = params.map((param: string) => {
return 'this.filterItems.' + param;
});
params.push('0'); // offset
params.push('1000'); // limit
let out = `private ${isSearch ? 'search' : 'get'}${uppercaseFirstLetter(pluralize(view.component))}() {
this.loading = true;
this.errorMessage = '';
${view.component}Service.${isSearch ? 'search' : 'get'}${uppercaseFirstLetter(pluralize(view.component))}(${params.join(', ')}).then((listItems: ${uppercaseFirstLetter(view.component)}Config[]) => {
// success
this.loading = false;
this.errorMessage = '';
this.items = listItems;
}).catch(this.handleError.bind(this));
}
`;
return out;
}
function createFEDeleteCode(view: EndpointDef) {
let out = `private deleteItem(itemKey: string) {
this.loading = true;
this.errorMessage = '';
${view.component}Service.delete${uppercaseFirstLetter(view.component)}(itemKey).then(() => {
this.loading = false;
this.errorMessage = '';
return this.get${uppercaseFirstLetter(pluralize(view.component))}();
}).catch(this.handleError.bind(this));
}
`;
return out;
}

View File

@ -0,0 +1,33 @@
import {ComponentDef, EndpointDef, SystemDef} from "../systemGenService";
import path from "path";
import fs from "fs";
export function buildRoute(component: ComponentDef, outDir: string, systemDef: SystemDef): Promise<void> {
let listEndpoints: EndpointDef[] = component.endpoints.filter((epd: EndpointDef) => {
return epd.type === 'list';
});
if (listEndpoints.length) {
return new Promise<void>((resolve) => {
const separator: string = `// SYSTE-BUILDER-routes`;
let fileLocation = path.join(process.cwd(), `frontend-${systemDef.name}`, 'src', 'router.ts');
let initServiceFile: string = fs.readFileSync(fileLocation, 'utf8');
let parts = initServiceFile.split(separator);
parts[0] = parts[0] + createRouteCode(component, systemDef);
let newServiceFile = parts.join(separator);
fs.writeFileSync(fileLocation, newServiceFile, 'utf8');
resolve();
});
}
return Promise.resolve();
}
function createRouteCode(component: ComponentDef, systemDef: SystemDef): string {
let out = `{
path: '/${component.component}',
name: '${component.component}',
component: () => import(/* webpackChunkName: "${component.component}" */ './components/${component.component}/${component.component}List.vue')
},
`;
return out;
}

View File

@ -0,0 +1,25 @@
import {ComponentDef, EndpointDef, SystemDef} from "../systemGenService";
import path from "path";
import fs from "fs";
import {createDetailsTypeValues} from "../helpers";
export function buildTypesView(component: ComponentDef, outDir: string, systemDef: SystemDef): Promise<void> {
if (component.endpoints.length) {
return insertFEDetailsCode(component.endpoints[0], outDir, systemDef);
} else {
return Promise.resolve();
}
}
function insertFEDetailsCode(view: EndpointDef, outDir: string, systemDef: SystemDef) {
return new Promise<void>((resolve) => {
const separator: string = `// SYSTEM-BUILDER-init-props`;
let fileLocation = path.join(outDir, `${view.component}Types.ts`)
let initServiceFile: string = fs.readFileSync(fileLocation, 'utf8');
let parts = initServiceFile.split(separator);
parts[0] = parts[0] + createDetailsTypeValues(view, systemDef);
let newServiceFile = parts.join(separator);
fs.writeFileSync(fileLocation, newServiceFile, 'utf8');
resolve();
});
}

209
src/helpers.ts Normal file
View File

@ -0,0 +1,209 @@
import fs from "fs";
import {
BelongsToDef,
ColumnDef,
ColumnRef,
EndpointDef,
Filter,
ManyToManyDef,
SystemDef,
TableDef
} from "./systemGenService";
import path from "path";
import {createServiceFunc} from "./views/service-creator";
import pluralize from "pluralize";
const INIT_VALUE = {
"blob": "''",
"boolean": "false",
"date": "''",
"dateTIME": "''",
"number": "0",
"string": "''"
};
const TYPE_VALUE = {
"blob": "string",
"boolean": "boolean",
"date": "string",
"dateTIME": "string",
"number": "number",
"string": "string",
};
function uppercaseFirstLetter(input: string) {
return input.charAt(0).toUpperCase() + input.slice(1);
}
function lowercaseFirstLetter(input: string) {
return input.charAt(0).toLowerCase() + input.slice(1);
}
function initializeComponentFile(sourceFile: string, destinationFile: string, component: string, outDir: string) {
return new Promise<void>((resolve, reject) => {
fs.rename(sourceFile, destinationFile, (err: any) => {
let fileContents = fs.readFileSync(destinationFile, 'utf8');
let uppercaseFirstLetterComponentName = uppercaseFirstLetter(component);
let lowercaseFirstLetterComponentName = lowercaseFirstLetter(component);
let lcPluralComponentName = lowercaseFirstLetter(pluralize(component));
let ucPluralComponentName = uppercaseFirstLetter(pluralize(component));
let newFileContents = fileContents.split('{{Component}}').join(uppercaseFirstLetterComponentName);
newFileContents = newFileContents.split('{{component}}').join(lowercaseFirstLetterComponentName);
newFileContents = newFileContents.split('{{components}}').join(lcPluralComponentName);
newFileContents = newFileContents.split('{{Components}}').join(ucPluralComponentName);
fs.writeFileSync(destinationFile, newFileContents, 'utf8');
resolve();
});
})
}
function removeTemplateFolder(destinationFolder: string) {
return new Promise<void>((resolve, reject) => {
fs.rmdir(destinationFolder, { recursive: true }, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
function removeTemplateFiles(destinationFolder: string) {
let regex = /\{\{component\}\}.*$/
fs.readdirSync(destinationFolder)
.filter(f => regex.test(f))
.map(f => fs.unlinkSync(path.join(destinationFolder, f)));
return Promise.resolve();
}
function insertServiceCode(view: EndpointDef, outDir: string): Promise<void> {
return new Promise<void>((resolve) => {
const separator: string = `// SYSTEM-BUILDER-${view.component}-service`;
let fileLocation = path.join(process.cwd(), outDir, 'src', 'components', view.component, `${view.component}Service.ts`);
let initServiceFile: string = fs.readFileSync(fileLocation, 'utf8');
let parts = initServiceFile.split(separator);
parts[0] = parts[0] + createServiceFunc(view);
let newServiceFile = parts.join(separator);
fs.writeFileSync(fileLocation, newServiceFile, 'utf8');
resolve();
});
}
function findColumn(col: ColumnRef, systemDef: SystemDef): ColumnDef | undefined {
let colDef: ColumnDef | undefined;
let table: TableDef | undefined = systemDef.storage.tables.find((td: TableDef) => {
return td.name === col.table;
});
if (table) {
colDef = table.columns.find((cd: ColumnDef) => {
return cd.name === col.name;
});
}
return colDef;
}
function makeSet<T extends {[id: string]: string}>(list: T[], id: string): T[] {
let typeObject: {[key: string]: T} = {};
for (let i in list) {
if (!typeObject[list[i][id]]) {
typeObject[list[i][id]] = list[i];
}
}
let typeSet: T[] = [];
for (let key in typeObject) {
typeSet.push(typeObject[key]);
}
return typeSet;
}
function createDetailsInitValues(view: EndpointDef, systemDef: SystemDef) {
let out = `${view.component}_id: '',
`;
view.columns.forEach((column: ColumnRef) => {
let colDef: ColumnDef | undefined = findColumn(column, systemDef);
if (colDef) {
out = out + `${colDef.name}: ${INIT_VALUE[colDef.type]},
`;
}
});
// TODO: get relations to add as properties
let storageTable: TableDef | undefined = systemDef.storage.tables.find((table: TableDef) => {
return table.name === view.component;
});
if (storageTable) {
storageTable.relations.forEach((rel: BelongsToDef) => {
out = out + `${rel.table}_id: '',
`;
});
}
systemDef.storage.relations.forEach((rel:ManyToManyDef) => {
if (rel.right === view.component) {
out = out + `${rel.left}_id: '',
`;
}
if (rel.left === view.component) {
out = out + `${rel.right}_id: '',
`;
}
});
return out;
}
function createDetailsTypeValues(view: EndpointDef, systemDef: SystemDef) {
let out = ``;
let storageTable = systemDef.storage.tables.find((table: TableDef) => {
return table.name === view.component;
});
let relations: string[] = [];
if (storageTable) {
storageTable.relations.map((rel: BelongsToDef) => {
relations.push(rel.table);
});
systemDef.storage.relations.forEach((rel: ManyToManyDef) => {
// @ts-ignore
if (rel.left === storageTable.name) {
relations.push(rel.right);
}
// @ts-ignore
if (rel.right === storageTable.name) {
relations.push(rel.left);
}
});
}
view.columns.forEach((column: ColumnRef) => {
let colDef: ColumnDef | undefined = findColumn(column, systemDef);
if (colDef) {
out = out + `${colDef.name}: ${TYPE_VALUE[colDef.type]};
`;
}
});
relations.forEach((rel: string) => {
out = out + `${rel}_id: string
`;
});
return out;
}
function componentDestination(systemDefName: string, componentName: string) {
return path.join(process.cwd(), `frontend-${systemDefName}`, 'src', 'components', componentName);
}
export {
componentDestination,
createDetailsInitValues,
createDetailsTypeValues,
findColumn,
initializeComponentFile,
insertServiceCode,
lowercaseFirstLetter,
makeSet,
removeTemplateFiles,
removeTemplateFolder,
uppercaseFirstLetter,
}

View File

@ -2,12 +2,15 @@ import * as path from 'path';
const ncp = require('ncp').ncp;
import { createDatabase, writeMigrationsToFile } from './database/database-creator';
import { createViews } from './views/views-creator';
import {createFrontend} from "./frontend-services/fe-service-creator";
import {removeTemplateFolder} from "./helpers";
class SystemGenService {
public runSysGen(defs: SystemDef) {
ncp(path.join(process.cwd(), 'frame'), path.join(process.cwd(), defs.name), (err: any) => {
if (err) {
console.log("error in ncp");
console.log(err);
} else {
console.log('success copying files');
@ -15,6 +18,16 @@ class SystemGenService {
this.buildViews(defs, defs.name);
}
});
ncp(path.join(process.cwd(), 'frontend-frame'), path.join(process.cwd(), `frontend-${defs.name}`), (err: any) => {
if (err) {
console.log("error in frontend ncp");
console.log(err);
} else {
console.log("success copying frontend files");
this.buildFrontend(defs);
}
});
}
public buildDatabase(storage: StorageDef, outDir: string) {
@ -25,6 +38,15 @@ class SystemGenService {
public buildViews(systemDef: SystemDef, outDir: string) {
createViews(systemDef);
}
public buildFrontend(systemDef: SystemDef) {
createFrontend(systemDef);
}
public cleanup() {
// TODO: remove 'component/{{component}}' folder
}
}
const systemGenService = new SystemGenService();
@ -34,7 +56,7 @@ interface SystemDef {
name: string;
storage: StorageDef;
components: ComponentDef[];
// TODO: add Views, ACLs, Behaviors, UX
// TODO: add ACLs, Behaviors, UX
}
interface StorageDef {
@ -50,7 +72,7 @@ interface ComponentDef {
interface EndpointDef {
component: string;
table: string;
type: ('list' | 'count' | 'item' | 'distinct' | 'update' | 'create' | 'delete' | 'search');
type: ('list' | 'count' | 'item' | 'update' | 'create' | 'delete' | 'search');
columns: ColumnRef[];
values?: ValueDef[];
join?: JoinDef[];
@ -67,8 +89,9 @@ interface Order {
interface Filter {
param: string; // the query param used to get the value
column: ColumnRef;
comparison: '=' | '!=' | '>' | '<' | 'contains';
comparison: '=' | '!=' | '>' | '<' | 'contains' | 'LIKE';
required?: boolean;
type?: string;
}
interface TableDef {
@ -91,6 +114,7 @@ interface ColumnRef {
table: string;
param?: string;
required?: boolean;
type?: string;
}
interface ValueDef {

View File

@ -1,14 +0,0 @@
function uppercaseFirstLetter(input: string)
{
return input.charAt(0).toUpperCase() + input.slice(1);
}
function lowercaseFirstLetter(input: string)
{
return input.charAt(0).toLowerCase() + input.slice(1);
}
export {
uppercaseFirstLetter,
lowercaseFirstLetter
}

View File

@ -1,11 +1,13 @@
import {ColumnRef, EndpointDef, Filter, JoinDef, Order} from "../systemGenService";
import pluralize from "pluralize";
import {uppercaseFirstLetter, lowercaseFirstLetter} from "./helpers";
import {uppercaseFirstLetter, lowercaseFirstLetter} from "../helpers";
function createMapperFunc(view: EndpointDef): string {
let func: string = '';
if (view.type === 'list' || view.type === 'count') {
func = buildGetList(view);
} else if (view.type === 'search') {
func = buildGetList(view, true);
} else if (view.type === 'item') {
func = buildGetItem(view);
} else if (view.type === 'update') {
@ -20,12 +22,9 @@ function createMapperFunc(view: EndpointDef): string {
function buildDeleteItem(view: EndpointDef): string {
let func: string = '';
let funcName = buildFunctionName(view);
let funcName = buildMapperFunctionName(view);
let tables: string = `${view.table}`;
let filtersObj = buildFilters(view);
let filters: string = filtersObj.filters;
let requiredFilterValues: string = filtersObj.requiredFilterValues;
let optionalFilterParts = filtersObj.optionalFiltersCode;
let {filters, requiredQueryValues, optionalFilterParts} = constructFilters(view);
let query: string = `DELETE FROM ${tables}${filters}`;
let funcParams = 'params: {[key: string]: string}';
@ -33,7 +32,7 @@ function buildDeleteItem(view: EndpointDef): string {
public ${funcName}(${funcParams}) {
let query: string = '${query}';
let queryValues: string[] = [${requiredFilterValues}];
let queryValues: (string | number)[] = [${requiredQueryValues}];
// optional params?
${optionalFilterParts}
@ -46,13 +45,10 @@ function buildDeleteItem(view: EndpointDef): string {
function buildGetItem(view: EndpointDef): string {
let func: string = '';
let funcName = buildFunctionName(view);
let funcName = buildMapperFunctionName(view);
let columns: string = `${view.component}.${view.component}_id, ${view.columns.map(c => `${c.table}.${c.name}`).join(', ')}`;
let tables: string = view.join?.length ? buildJoin(view) : `${view.table}`;
let filtersObj = buildFilters(view);
let filters: string = filtersObj.filters;
let requiredFilterValues: string = filtersObj.requiredFilterValues;
let optionalFilterParts = filtersObj.optionalFiltersCode;
let {filters, requiredQueryValues, optionalFilterParts} = constructFilters(view);
let query: string = `SELECT ${columns} FROM ${tables}${filters} LIMIT 1`;
let funcParams = 'params: {[key: string]: string}';
@ -60,7 +56,7 @@ function buildGetItem(view: EndpointDef): string {
public ${funcName}(${funcParams}) {
let query: string = '${query}';
let queryValues: string[] = [${requiredFilterValues}];
let queryValues: (string | number)[] = [${requiredQueryValues}];
// optional params?
${optionalFilterParts}
@ -73,23 +69,60 @@ function buildGetItem(view: EndpointDef): string {
function buildCreateFunc(view: EndpointDef): string {
let func: string = '';
let query: string = `INSERT INTO ${view.table} SET `;
func = getCreateOrUpdateFunc(query, view);
let inQuery: string = `INSERT INTO ${view.table} SET `;
let {funcName, query, requiredQueryValues, optionalValuesCode} = getCreateOrUpdateFunc(inQuery, view);
func = `
public ${funcName}(params: {[key: string]: string}) {
let query: string = '${query}';
let queryValues: (string | number)[] = [${requiredQueryValues}];
// optional params?
${optionalValuesCode}
return super.runQuery(query, [...queryValues]);
}
`;
return func;
}
function buildUpdateFunc(view: EndpointDef): string {
let func: string = '';
let query: string = `UPDATE ${view.table} SET `;
func = getCreateOrUpdateFunc(query, view);
let inQuery: string = `UPDATE ${view.table} SET `;
let {funcName, query, requiredQueryValues, optionalValuesCode} = getCreateOrUpdateFunc(inQuery, view);
let postQuery = ` WHERE `;
for (let i in view.filters) {
// @ts-ignore
let filter: Filter = view.filters[i];
if (!postQuery.endsWith('WHERE ')) {
postQuery = postQuery + ` AND `;
}
postQuery = postQuery + `${filter.column.table}.${filter.column.name} = ?`;
if (requiredQueryValues.length) {
requiredQueryValues = requiredQueryValues + `, `;
}
requiredQueryValues = requiredQueryValues + `params.${filter.param}`;
}
func = `
public ${funcName}(params: {[key: string]: string}) {
let query: string = '${query}';
let queryValues: (string | number)[] = [${requiredQueryValues}];
// optional params?
${optionalValuesCode}
query = query + '${postQuery}';
return super.runQuery(query, [...queryValues]);
}
`;
return func;
}
function getCreateOrUpdateFunc(query: string, view: EndpointDef): string {
let func: string = '';
let funcName = buildFunctionName(view);
function getCreateOrUpdateFunc(query: string, view: EndpointDef) {
let funcName = buildMapperFunctionName(view);
let requiredQueryValues: string = '';
let optionalValuesCode: string = '';
for (let i in view.columns) {
@ -112,33 +145,11 @@ function getCreateOrUpdateFunc(query: string, view: EndpointDef): string {
`;
}
}
func = `
public ${funcName}(params: {[key: string]: string}) {
let query: string = '${query}';
let queryValues: string[] = [${requiredQueryValues}];
// optional params?
${optionalValuesCode}
return super.runQuery(query, [...queryValues]);
}
`;
return func;
return {funcName, query, requiredQueryValues, optionalValuesCode};
}
function buildGetList(view: EndpointDef): string {
// TODO: optionally allow a raw query definition with predefined params
// This would be so much simpler and easy if it were just the queries wanted
let func: string = '';
let funcName: string = '';
let columns: string = view.type === 'count' ? `count(*)` : `${view.component}.${view.component}_id, ${view.columns.map(c => `${c.table}.${c.name}`).join(', ')}`;
let query: string = '';
let tables: string = view.join?.length ? buildJoin(view) : `${view.table}`;
function constructFilters(view: EndpointDef, isSearch=false) {
let filters: string = '';
funcName = buildFunctionName(view);
let requiredFilterStrings = [];
let requiredQueryValues = '';
let optionalFilterParts = '';
@ -160,13 +171,27 @@ function buildGetList(view: EndpointDef): string {
*/
optionalFilterParts = optionalFilterParts + `
if (params.${f.param}) {
query = query + ' AND ${f.column.table}.${f.column.name} ${f.comparison} ?';
queryValues.push(params.${f.param});
query = query + ' ${isSearch ? 'OR' : 'AND'} ${f.column.table}.${f.column.name} ${f.comparison} ?';
queryValues.push(${isSearch ? '\'%\' + params.' + f.param + ' + \'%\'' : 'params.' + f.param});
}`;
}
}
filters = ` WHERE ${requiredFilterStrings.join(' AND ')}`;
}
return {filters, requiredQueryValues, optionalFilterParts};
}
function buildGetList(view: EndpointDef, isSearch=false): string {
// TODO: optionally allow a raw query definition with predefined params
// This would be so much simpler and easy if it were just the queries wanted
let func: string = '';
let funcName: string = '';
let columns: string = view.type === 'count' ? `count(*) as Count` : `${view.component}.${view.component}_id, ${view.columns.map(c => `${c.table}.${c.name}`).join(', ')}`;
let query: string = '';
let tables: string = view.join?.length ? buildJoin(view) : `${view.table}`;
funcName = buildMapperFunctionName(view);
let {filters, requiredQueryValues, optionalFilterParts} = constructFilters(view, isSearch);
query = `SELECT ${columns} FROM ${tables}${filters}`;
let orderByCode = '';
@ -176,10 +201,10 @@ function buildGetList(view: EndpointDef): string {
`;
}
let funcParams = 'params: {[key: string]: string}, offset: number, limit: number';
let limitOrderCode = `query = query + ' LIMIT ?, ?';
queryValues.push(offset.toString(10));
queryValues.push(limit.toString(10));
${orderByCode}`;
let limitOrderCode = `${orderByCode}
query = query + ' LIMIT ?, ?';
queryValues.push(offset);
queryValues.push(limit);`;
if (view.type === 'count') {
funcParams = 'params: {[key: string]: string}';
limitOrderCode = ``;
@ -189,7 +214,7 @@ function buildGetList(view: EndpointDef): string {
public ${funcName}(${funcParams}) {
let query: string = '${query}';
let queryValues: string[] = [${requiredQueryValues}];
let queryValues: (string | number)[] = [${requiredQueryValues}];
// optional params?
${optionalFilterParts}
@ -200,49 +225,6 @@ function buildGetList(view: EndpointDef): string {
return func;
}
function buildFilters(view: EndpointDef): {filters: string, requiredFilterValues: string, optionalFiltersCode: string} {
let filtersObj: {filters: string, requiredFilterValues: string, optionalFiltersCode: string} = {
filters: '',
requiredFilterValues: '',
optionalFiltersCode: ''
};
let filters: string = '';
let requiredFilterStrings = [];
let requiredQueryValues = '';
let optionalFilterParts = '';
if (view.filters) {
for (let i in view.filters) {
let f: Filter = view.filters[i];
if (f.required) {
requiredFilterStrings.push(`${f.column.table}.${f.column.name} ${f.comparison} ?`); // e.g. 'table.id = ?'
if (requiredQueryValues.length) {
requiredQueryValues = requiredQueryValues + `, `;
}
requiredQueryValues = requiredQueryValues + `params.${f.param}`;
} else {
/* e.g.
if (eventLog.app) {
query = query + ', app = ?';
queryValues.push(eventLog.app);
}
*/
optionalFilterParts = optionalFilterParts + `
if (params.${f.param}) {
query = query + ' AND ${f.column.table}.${f.column.name} ${f.comparison} ?';
queryValues.push(params.${f.param});
}`;
}
}
filters = ` WHERE ${requiredFilterStrings.join(' AND ')}`;
}
filtersObj.filters = filters;
filtersObj.requiredFilterValues = requiredQueryValues;
filtersObj.optionalFiltersCode = optionalFilterParts;
return filtersObj;
}
function buildJoin(view: EndpointDef): string {
if (view.join?.length) {
let join = `${view.table}`;
@ -270,7 +252,7 @@ function buildOrderBy(view: EndpointDef): string {
return orderBy;
}
function buildFunctionName(view: EndpointDef): string {
function buildMapperFunctionName(view: EndpointDef): string {
let selector: {[type: string]: (view: EndpointDef) => string} = {
'list': (view: EndpointDef) => `get${pluralize(uppercaseFirstLetter(view.component))}` + (view.type === 'count' ? 'Count' : ''),
'count': (view: EndpointDef) => `get${pluralize(uppercaseFirstLetter(view.component))}` + (view.type === 'count' ? 'Count' : ''),
@ -278,11 +260,12 @@ function buildFunctionName(view: EndpointDef): string {
'update': (view: EndpointDef) => `update${uppercaseFirstLetter(view.component)}`,
'create': (view: EndpointDef) => `create${uppercaseFirstLetter(view.component)}`,
'delete': (view: EndpointDef) => `delete${uppercaseFirstLetter(view.component)}`,
'search': (view: EndpointDef) => `search${uppercaseFirstLetter(view.component)}`,
'search': (view: EndpointDef) => `search${pluralize(uppercaseFirstLetter(view.component))}`,
};
return selector[view.type](view);
}
export {
createMapperFunc
createMapperFunc,
buildMapperFunctionName
}

View File

@ -0,0 +1,76 @@
import {EndpointDef} from "../systemGenService";
import {lowercaseFirstLetter, uppercaseFirstLetter} from "../helpers";
import {buildServiceFunctionName, getFuncParams} from "./service-creator";
import pluralize from "pluralize";
const METHOD = {
'count': 'post',
'create': 'post',
'delete': 'delete',
'item': 'get',
'list': 'post',
'search': 'post',
'update': 'put',
}
const URL = {
'count': (view: EndpointDef) => {
return `count${uppercaseFirstLetter(pluralize(view.component))}`;
},
'create': (view: EndpointDef) => {
return `${view.component}`;
},
'delete': (view: EndpointDef) => {
// TODO: needs the params added to select the right one (id)
return `${view.component}/:${view.component}_id`;
},
'item': (view: EndpointDef) => {
// TODO: needs the params added to select the right one (id)
return `${view.component}/:${view.component}_id`;
},
'list': (view: EndpointDef) => {
return `${pluralize(view.component)}`;
},
'search': (view: EndpointDef) => {
return `search${uppercaseFirstLetter(pluralize(view.component))}`;
},
'update': (view: EndpointDef) => {
// TODO: needs the params added to select the right one (id)
return `${view.component}/:${view.component}_id`;
},
}
function createRoutesFunc(view: EndpointDef): string {
let params = getFuncParams(view);
let isListOrSearch: boolean = view.type === 'list' || view.type === 'search';
if (isListOrSearch) {
params.push('offset');
params.push('limit');
}
// TODO: get requests don't have req.body so the params need to come from the url
let func: string = `
router.${METHOD[view.type]}('/${URL[view.type](view)}', (req, res, next) => {
${serviceInstance(view)}.${buildServiceFunctionName(view)}(${(params.length ? 'req.body.' : '') + params.join(', req.body.')}).then((data) => {
res.status(200);
res.json(data);
}).catch((err) => {
res.status(500);
res.json({
status: 'error',
message: err,
});
});
});
`;
return func;
}
function serviceInstance(view: EndpointDef): string {
return `${lowercaseFirstLetter(view.component)}Service`;
}
export {
createRoutesFunc,
METHOD,
URL
}

View File

@ -0,0 +1,128 @@
import {BelongsToDef, ColumnDef, EndpointDef, Filter, ManyToManyDef, SystemDef, TableDef} from "../systemGenService";
import {lowercaseFirstLetter} from "../helpers";
import {buildMapperFunctionName} from "./mapper-creator";
function createServiceFunc(view: EndpointDef) {
// TODO: add support for doing things other than talking to the mapper e.g. API calls and cache checks
return buildServiceFunction(view);
}
function buildServiceFunction(view: EndpointDef): string {
let func: string = '';
let isListOrSearch: boolean = view.type === 'list' || view.type === 'search';
let funcName: string = buildServiceFunctionName(view);
let funcParams: string[] = getFuncParams(view);
let columnParams: string[] = view.columns?.filter(column => column.param).map(column => { return '\n ' + column.param + ': ' + column.param; });
let filterParams: string[] = view.filters?.filter(filter => filter.param).map(filter => { return '\n ' + filter.param + ': ' + filter.param; }) || [];
if (isListOrSearch) {
funcParams.push('offset');
funcParams.push('limit');
}
func = `
public ${funcName}(${funcParams.join(', ')}) {
let params = {${columnParams}${columnParams.length ? ',' : ''}${filterParams}
};
return this.${mapperInstance(view)}.${buildMapperFunctionName(view)}(params${isListOrSearch ? ', offset, limit' : ''});
}
`;
return func;
}
function getFuncParams(view: EndpointDef, addTypes=false, systemDef?: SystemDef): string[] {
let filters = view.filters || [];
let columnList: ParamDef[] = view.columns?.filter((column) => {return column.param;}).map((column) => {
return {
param: column.param,
required: column.required,
type: column.type
};
});
let filterList: ParamDef[] = filters.filter((column) => {return column.param;}).map((filter) => {
return {
param: filter.param,
required: filter.required,
type: filter.type
};
});
let relationsList: ParamDef[] = [];
if (systemDef) {
let storageTable: TableDef | undefined = systemDef.storage.tables.find((table: TableDef) => {
return view.component === table.name;
});
if (storageTable) {
relationsList = storageTable.relations.map((rel: BelongsToDef) => {
return {
param: rel.table + '_id',
required: false,
type: 'string'
}
});
}
relationsList = [...relationsList, ...systemDef.storage.relations.filter((rel: ManyToManyDef) => {
if (rel.left === view.component || rel.right === view.component) {
return true;
}
return false;
}).map((rel: ManyToManyDef) => {
if (rel.left === view.component) {
return {
param: rel.right + '_id',
required: false,
type: 'string'
};
} else {
return {
param: rel.left + '_id',
required: false,
type: 'string'
};
}
})];
}
// funcParams should be sorted by 'required' so the optional params can come last and be left off when calling
let combinedList = relationsList.concat(columnList).concat(filterList).sort((a, b) => {
if (a.required && b.required) {
return 0;
}
if (a.required) {
return -1;
}
if (b.required) {
return 1;
}
return 0;
});
return combinedList.map((param) => {
if (param.type && addTypes) {
return `${param.param}: ${param.type}`;
}
return param.param || ''; // both lists have already been filtered so the value should never return as an empty string
});
}
function buildServiceFunctionName(view: EndpointDef): string {
return buildMapperFunctionName(view); // the functions should have the same name... I'm pretty sure...
}
function mapperInstance(view: EndpointDef): string {
return `${lowercaseFirstLetter(view.component)}Mapper`;
}
interface ParamDef {
param?: string;
required?: boolean;
type?: string;
}
export {
createServiceFunc,
getFuncParams,
buildServiceFunctionName
}

View File

@ -6,7 +6,8 @@ import {
import * as path from 'path';
import * as fs from 'fs';
import {createMapperFunc} from "./mapper-creator";
import {uppercaseFirstLetter, lowercaseFirstLetter} from "./helpers";
import {initializeComponentFile, insertServiceCode} from "../helpers";
import {createRoutesFunc} from "./routes-creator";
const ncp = require('ncp').ncp;
@ -24,6 +25,8 @@ function createComponent(component: ComponentDef, systemDef: SystemDef) {
ncp(path.join(process.cwd(), 'frame', 'src', 'components', '{{component}}'), path.join(process.cwd(), systemDef.name, 'src', 'components', component.component), (err: any) => {
if (err) {
console.log(err);
componentReject();
return;
} else {
let mapperPromise = initializeComponentFile(path.join(process.cwd(), systemDef.name, 'src', 'components', component.component, '{{component}}Mapper.ets'), path.join(process.cwd(), systemDef.name, 'src', 'components', component.component, `${component.component}Mapper.ts`), component.component, systemDef.name);
for (let i in component.endpoints) {
@ -32,9 +35,19 @@ function createComponent(component: ComponentDef, systemDef: SystemDef) {
});
}
let routesPromise = initializeComponentFile(path.join(process.cwd(), systemDef.name, 'src', 'components', component.component, '{{component}}Routes.ets'), path.join(process.cwd(), systemDef.name, 'src', 'components', component.component, `${component.component}Routes.ts`), component.component, systemDef.name);
for (let i in component.endpoints) {
routesPromise = routesPromise.then(() => {
return insertRoutesCode(component.endpoints[i], systemDef.name);
})
}
addInitializeRoutesCode(component.component, systemDef.name);
let serviceFileLocation: string = path.join(process.cwd(), systemDef.name, 'src', 'components', component.component, `${component.component}Service.ts`);
let servicePromise = initializeComponentFile(path.join(process.cwd(), systemDef.name, 'src', 'components', component.component, '{{component}}Service.ets'), serviceFileLocation, component.component, systemDef.name);
for (let i in component.endpoints) {
servicePromise = servicePromise.then(() => {
return insertServiceCode(component.endpoints[i], systemDef.name);
});
}
addInitializeServiceCode(component.component, systemDef.name);
Promise.all([mapperPromise, routesPromise, servicePromise]).then(() => {
console.log(`success creating component ${component.component}`);
@ -45,20 +58,6 @@ function createComponent(component: ComponentDef, systemDef: SystemDef) {
});
}
function initializeComponentFile(sourceFile: string, destinationFile: string, component: string, outDir: string) {
return new Promise<void>((resolve, reject) => {
fs.rename(sourceFile, destinationFile, (err: any) => {
let fileContents = fs.readFileSync(destinationFile, 'utf8');
var uppercaseFirstLetterComponentName = uppercaseFirstLetter(component);
var lowercaseFirstLetterComponentName = lowercaseFirstLetter(component);
let newFileContents = fileContents.split('{{Component}}').join(uppercaseFirstLetterComponentName);
newFileContents = newFileContents.split('{{component}}').join(lowercaseFirstLetterComponentName);
fs.writeFileSync(destinationFile, newFileContents, 'utf8');
resolve();
});
})
}
function addInitializeServiceCode(component: string, outDir: string) {
let fileLocation = path.join(process.cwd(), outDir, 'src', 'initializeServices.ts');
let initServicesFile: string = fs.readFileSync(fileLocation, 'utf8');
@ -99,3 +98,17 @@ function insertMapperCode(view: EndpointDef, outDir: string): Promise<void> {
resolve();
});
}
function insertRoutesCode(view: EndpointDef, outDir: string): Promise<void> {
return new Promise<void>((resolve) => {
const separator: string = `// SYSTEM-BUILDER-${view.component}-routes`;
let fileLocation = path.join(process.cwd(), outDir, 'src', 'components', view.component, `${view.component}Routes.ts`);
let initRoutesFile: string = fs.readFileSync(fileLocation, 'utf8');
let parts = initRoutesFile.split(separator);
parts[0] = parts[0] + createRoutesFunc(view);
let newRoutesFile = parts.join(separator);
fs.writeFileSync(fileLocation, newRoutesFile, 'utf8');
resolve();
});
}

View File

@ -46,7 +46,8 @@
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
"esModuleInterop": true,
/* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
/* Source Map Options */
@ -58,6 +59,12 @@
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
"lib": [
"dom",
"es5",
"scripthost",
"es2015.promise"
]
},
"include": ["src"],
"exclude": ["node_modules", "**/__tests__/*"]