add mapper builders
still needs all mapper versions but has list, create, and count working
This commit is contained in:
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Datasource local storage ignored files
|
||||
/../../../../../../:\Users\gomas\src\system-builder\.idea/dataSources/
|
||||
/dataSources.local.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/system-builder.iml" filepath="$PROJECT_DIR$/.idea/system-builder.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
6
.idea/sqldialects.xml
generated
Normal file
6
.idea/sqldialects.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="SqlDialectMappings">
|
||||
<file url="PROJECT" dialect="MariaDB" />
|
||||
</component>
|
||||
</project>
|
9
.idea/system-builder.iml
generated
Normal file
9
.idea/system-builder.iml
generated
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
142
dist/example-def.js
vendored
142
dist/example-def.js
vendored
@ -1,142 +0,0 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var def = {
|
||||
name: 'Todo',
|
||||
storage: {
|
||||
tables: [
|
||||
{
|
||||
name: "task",
|
||||
relations: [
|
||||
{
|
||||
type: 'belongs-to',
|
||||
table: 'list',
|
||||
}
|
||||
],
|
||||
columns: [
|
||||
{
|
||||
name: "name",
|
||||
type: "string",
|
||||
nullable: false,
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
type: "string",
|
||||
nullable: true,
|
||||
},
|
||||
{
|
||||
name: "completed",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
},
|
||||
{
|
||||
name: "completed_date",
|
||||
type: "date",
|
||||
nullable: true,
|
||||
},
|
||||
{
|
||||
name: "metadata",
|
||||
type: "blob",
|
||||
nullable: true,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "list",
|
||||
relations: [],
|
||||
columns: [
|
||||
{
|
||||
name: "name",
|
||||
type: "string",
|
||||
unique: false,
|
||||
nullable: false,
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "user",
|
||||
relations: [],
|
||||
columns: [
|
||||
{
|
||||
name: "name",
|
||||
type: "string",
|
||||
nullable: false,
|
||||
},
|
||||
{
|
||||
name: "password",
|
||||
type: "string",
|
||||
nullable: false,
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
relations: [
|
||||
{
|
||||
left: 'list',
|
||||
relation: 'many-to-many',
|
||||
right: 'user',
|
||||
columns: [
|
||||
{
|
||||
name: 'access',
|
||||
type: 'string',
|
||||
unique: false,
|
||||
nullable: false,
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
views: [
|
||||
{
|
||||
component: 'task',
|
||||
type: ['list', 'count'],
|
||||
columns: [
|
||||
'name',
|
||||
'description',
|
||||
'completed',
|
||||
'completed_date',
|
||||
],
|
||||
orderBy: [
|
||||
{
|
||||
column: 'modified',
|
||||
direction: 'desc'
|
||||
}
|
||||
],
|
||||
filters: [
|
||||
{
|
||||
param: 'list',
|
||||
column: 'list_id',
|
||||
comparison: '=',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
param: 'completed',
|
||||
column: 'completed',
|
||||
comparison: '=',
|
||||
required: false
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
component: 'list',
|
||||
type: ['list', 'count'],
|
||||
columns: [
|
||||
'name',
|
||||
],
|
||||
orderBy: [
|
||||
{
|
||||
column: 'modified',
|
||||
direction: 'desc'
|
||||
}
|
||||
],
|
||||
filters: [
|
||||
{
|
||||
param: 'user',
|
||||
column: 'user_id',
|
||||
comparison: '=',
|
||||
required: true
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
};
|
||||
exports.default = def;
|
@ -1,8 +1,10 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@types/node": "^14.0.13",
|
||||
"@types/pluralize": "^0.0.29",
|
||||
"ncp": "^2.0.0",
|
||||
"path": "^0.12.7"
|
||||
"path": "^0.12.7",
|
||||
"pluralize": "^8.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "rm -rf dist && tsc && cp -r frame dist/ && cd dist && node index.js"
|
||||
|
@ -3,7 +3,7 @@ import * as fs from 'fs';
|
||||
import {
|
||||
SystemDef,
|
||||
StorageDef,
|
||||
ViewDef,
|
||||
EndpointDef,
|
||||
Order,
|
||||
Filter,
|
||||
TableDef,
|
||||
|
@ -27,6 +27,7 @@ let def: SystemDef = {
|
||||
name: "completed",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
default: 'false'
|
||||
},
|
||||
{
|
||||
name: "completed_date",
|
||||
@ -85,53 +86,189 @@ let def: SystemDef = {
|
||||
}
|
||||
]
|
||||
},
|
||||
views: [
|
||||
components: [
|
||||
{
|
||||
component: 'task',
|
||||
type: ['list', 'count'],
|
||||
endpoints: [
|
||||
{
|
||||
component: 'task',
|
||||
table: 'task',
|
||||
type: 'list',
|
||||
columns: [
|
||||
'name',
|
||||
'description',
|
||||
'completed',
|
||||
'completed_date',
|
||||
{
|
||||
name: 'name',
|
||||
table: 'task'
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
table: 'task'
|
||||
},
|
||||
{
|
||||
name: 'completed',
|
||||
table: 'task'
|
||||
},
|
||||
{
|
||||
name: 'completed_date',
|
||||
table: 'task'
|
||||
},
|
||||
],
|
||||
orderBy: [
|
||||
{
|
||||
column: 'modified',
|
||||
column: {
|
||||
name: 'modified',
|
||||
table: 'task'
|
||||
},
|
||||
direction: 'desc'
|
||||
}
|
||||
],
|
||||
filters: [
|
||||
{
|
||||
param: 'list',
|
||||
column: 'list_id',
|
||||
column: {
|
||||
name: 'list_id',
|
||||
table: 'task'
|
||||
},
|
||||
comparison: '=',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
param: 'completed',
|
||||
column: 'completed',
|
||||
column: {
|
||||
name: 'completed',
|
||||
table: 'task'
|
||||
},
|
||||
comparison: '=',
|
||||
required: false
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
component: 'list',
|
||||
type: ['list', 'count'],
|
||||
component: 'task',
|
||||
table: 'task',
|
||||
type: 'count',
|
||||
columns: [
|
||||
'name',
|
||||
{
|
||||
name: 'name',
|
||||
table: 'task'
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
table: 'task'
|
||||
},
|
||||
{
|
||||
name: 'completed',
|
||||
table: 'task'
|
||||
},
|
||||
{
|
||||
name: 'completed_date',
|
||||
table: 'task'
|
||||
},
|
||||
],
|
||||
orderBy: [
|
||||
{
|
||||
column: 'modified',
|
||||
column: {
|
||||
name: 'modified',
|
||||
table: 'task'
|
||||
},
|
||||
direction: 'desc'
|
||||
}
|
||||
],
|
||||
filters: [
|
||||
{
|
||||
param: 'list',
|
||||
column: {
|
||||
name: 'list_id',
|
||||
table: 'task'
|
||||
},
|
||||
comparison: '=',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
param: 'completed',
|
||||
column: {
|
||||
name: 'completed',
|
||||
table: 'task'
|
||||
},
|
||||
comparison: '=',
|
||||
required: false
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
component: 'task',
|
||||
table: 'task',
|
||||
type: 'create',
|
||||
columns: [
|
||||
{
|
||||
name: 'name',
|
||||
table: 'task',
|
||||
param: 'name',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'list_id',
|
||||
table: 'task',
|
||||
param: 'list',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
table: 'task',
|
||||
param: 'description'
|
||||
},
|
||||
{
|
||||
name: 'completed',
|
||||
table: 'task',
|
||||
param: 'completed'
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
component: 'list',
|
||||
endpoints: [
|
||||
{
|
||||
component: 'list',
|
||||
table: 'list',
|
||||
type: 'list',
|
||||
columns: [
|
||||
{
|
||||
name: 'name',
|
||||
table: 'list'
|
||||
},
|
||||
],
|
||||
join: [
|
||||
{
|
||||
table: 'list_user',
|
||||
on: {
|
||||
left: {
|
||||
name: 'list_id',
|
||||
table: 'list'
|
||||
},
|
||||
right: {
|
||||
name: 'list_id',
|
||||
table: 'list_user'
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
orderBy: [
|
||||
{
|
||||
column: {
|
||||
name: 'modified',
|
||||
table: 'list'
|
||||
},
|
||||
direction: 'desc'
|
||||
}
|
||||
],
|
||||
filters: [
|
||||
{
|
||||
param: 'user',
|
||||
column: 'user_id',
|
||||
column: {
|
||||
name: 'user_id',
|
||||
table: 'list_user'
|
||||
},
|
||||
comparison: '=',
|
||||
required: true
|
||||
},
|
||||
@ -139,5 +276,7 @@ let def: SystemDef = {
|
||||
},
|
||||
]
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
export default def;
|
||||
|
@ -33,7 +33,7 @@ const systemGenService = new SystemGenService();
|
||||
interface SystemDef {
|
||||
name: string;
|
||||
storage: StorageDef;
|
||||
views: ViewDef[];
|
||||
components: ComponentDef[];
|
||||
// TODO: add Views, ACLs, Behaviors, UX
|
||||
}
|
||||
|
||||
@ -42,25 +42,32 @@ interface StorageDef {
|
||||
relations: ManyToManyDef[];
|
||||
}
|
||||
|
||||
interface ViewDef {
|
||||
interface ComponentDef {
|
||||
component: string;
|
||||
type: ('list' | 'count' | 'item' | 'distinct')[];
|
||||
columns: string[];
|
||||
endpoints: EndpointDef[];
|
||||
}
|
||||
|
||||
interface EndpointDef {
|
||||
component: string;
|
||||
table: string;
|
||||
type: ('list' | 'count' | 'item' | 'distinct' | 'update' | 'create' | 'delete');
|
||||
columns: ColumnRef[];
|
||||
values?: ValueDef[];
|
||||
join?: JoinDef[];
|
||||
orderBy?: Order[];
|
||||
filters?: Filter[];
|
||||
// if type is 'list' it will always include skip and limit for pagination
|
||||
}
|
||||
|
||||
interface Order {
|
||||
column: string;
|
||||
column: ColumnRef;
|
||||
direction: 'asc' | 'desc';
|
||||
}
|
||||
|
||||
interface Filter {
|
||||
param: string; // the query param used to get the value
|
||||
column: string;
|
||||
column: ColumnRef;
|
||||
comparison: '=' | '!=' | '>' | '<' | 'contains';
|
||||
value?: string;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
@ -79,6 +86,26 @@ interface ColumnDef {
|
||||
default?: string;
|
||||
}
|
||||
|
||||
interface ColumnRef {
|
||||
name: string;
|
||||
table: string;
|
||||
param?: string;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
interface ValueDef {
|
||||
column: string;
|
||||
required: boolean;
|
||||
}
|
||||
|
||||
interface JoinDef {
|
||||
table: string;
|
||||
on: {
|
||||
left: ColumnRef;
|
||||
right: ColumnRef;
|
||||
}
|
||||
}
|
||||
|
||||
interface BelongsToDef {
|
||||
type: 'belongs-to';
|
||||
table: string;
|
||||
@ -92,14 +119,17 @@ interface ManyToManyDef {
|
||||
}
|
||||
|
||||
export {
|
||||
SystemDef,
|
||||
StorageDef,
|
||||
ViewDef,
|
||||
Order,
|
||||
Filter,
|
||||
TableDef,
|
||||
ColumnDef,
|
||||
BelongsToDef,
|
||||
ColumnDef,
|
||||
ColumnRef,
|
||||
ComponentDef,
|
||||
EndpointDef,
|
||||
Filter,
|
||||
JoinDef,
|
||||
ManyToManyDef,
|
||||
Order,
|
||||
StorageDef,
|
||||
SystemDef,
|
||||
TableDef,
|
||||
systemGenService
|
||||
}
|
||||
|
@ -1,35 +1,44 @@
|
||||
import {
|
||||
SystemDef,
|
||||
ViewDef
|
||||
BelongsToDef,
|
||||
ColumnDef,
|
||||
Filter, JoinDef, Order,
|
||||
SystemDef, TableDef,
|
||||
EndpointDef, ComponentDef, ColumnRef
|
||||
} from '../systemGenService';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import pluralize from 'pluralize';
|
||||
|
||||
const ncp = require('ncp').ncp;
|
||||
|
||||
|
||||
export function createViews(systemDef: SystemDef) {
|
||||
let viewsPromises = [];
|
||||
for (let i in systemDef.views) {
|
||||
viewsPromises.push(createComponent(systemDef.views[i], systemDef.name));
|
||||
for (let i in systemDef.components) {
|
||||
viewsPromises.push(createComponent(systemDef.components[i], systemDef));
|
||||
}
|
||||
return Promise.all(viewsPromises);
|
||||
}
|
||||
|
||||
function createComponent(view: ViewDef, outDir: string) {
|
||||
let componentPromise = new Promise((componentResolve, componentReject) => {
|
||||
ncp(path.join(process.cwd(), 'frame', 'src', 'components', '{{component}}'), path.join(process.cwd(), outDir, 'src', 'components', view.component), (err: any) => {
|
||||
function createComponent(component: ComponentDef, systemDef: SystemDef) {
|
||||
let componentPromise = new Promise<void>((componentResolve, componentReject) => {
|
||||
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);
|
||||
} else {
|
||||
let mapperPromise = initializeComponentFile(path.join(process.cwd(), outDir, 'src', 'components', view.component, '{{component}}Mapper.ets'), path.join(process.cwd(), outDir, 'src', 'components', view.component, `${view.component}Mapper.ts`), view.component, outDir);
|
||||
let routesPromise = initializeComponentFile(path.join(process.cwd(), outDir, 'src', 'components', view.component, '{{component}}Routes.ets'), path.join(process.cwd(), outDir, 'src', 'components', view.component, `${view.component}Routes.ts`), view.component, outDir);
|
||||
addInitializeRoutesCode(view.component, outDir);
|
||||
let serviceFileLocation: string = path.join(process.cwd(), outDir, 'src', 'components', view.component, `${view.component}Service.ts`);
|
||||
let servicePromise = initializeComponentFile(path.join(process.cwd(), outDir, 'src', 'components', view.component, '{{component}}Service.ets'), serviceFileLocation, view.component, outDir);
|
||||
addInitializeServiceCode(view.component, outDir);
|
||||
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) {
|
||||
mapperPromise = mapperPromise.then(() => {
|
||||
return insertMapperCode(component.endpoints[i], systemDef.name);
|
||||
});
|
||||
}
|
||||
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);
|
||||
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);
|
||||
addInitializeServiceCode(component.component, systemDef.name);
|
||||
Promise.all([mapperPromise, routesPromise, servicePromise]).then(() => {
|
||||
console.log(`success creating component ${view.component}`);
|
||||
console.log(`success creating component ${component.component}`);
|
||||
componentResolve();
|
||||
});
|
||||
}
|
||||
@ -39,7 +48,7 @@ function createComponent(view: ViewDef, outDir: string) {
|
||||
}
|
||||
|
||||
function initializeComponentFile(sourceFile: string, destinationFile: string, component: string, outDir: string) {
|
||||
let filePromise = new Promise((resolve, reject) => {
|
||||
let filePromise = new Promise<void>((resolve, reject) => {
|
||||
fs.rename(sourceFile, destinationFile, (err: any) => {
|
||||
let fileContents = fs.readFileSync(destinationFile, 'utf8');
|
||||
var uppercaseFirstLetterComponentName = uppercaseFirstLetter(component);
|
||||
@ -81,6 +90,207 @@ function addInitializeRoutesCode(component: string, outDir: string) {
|
||||
fs.writeFileSync(fileLocation, newRoutesFile, 'utf8');
|
||||
}
|
||||
|
||||
function insertMapperCode(view: EndpointDef, outDir: string): Promise<void> {
|
||||
const filePromise: Promise<void> = new Promise<void>((resolve) => {
|
||||
const separator: string = `// SYSTEM-BUILDER-${view.component}-mapper`;
|
||||
let fileLocation = path.join(process.cwd(), outDir, 'src', 'components', view.component, `${view.component}Mapper.ts`);
|
||||
let initMapperFile: string = fs.readFileSync(fileLocation, 'utf8');
|
||||
let parts = initMapperFile.split(separator);
|
||||
// TODO: add mapper code here
|
||||
parts[0] = parts[0] + createMapperFunc(view);
|
||||
let newMapperFile = parts.join(separator);
|
||||
fs.writeFileSync(fileLocation, newMapperFile, 'utf8');
|
||||
resolve();
|
||||
});
|
||||
return filePromise;
|
||||
}
|
||||
|
||||
function createMapperFunc(view: EndpointDef): string {
|
||||
let func: string = '';
|
||||
let funcName: string = '';
|
||||
if (view.type.indexOf('list') !== -1 || view.type.indexOf('count') !== -1) {
|
||||
func = buildGetList(view);
|
||||
} else if (view.type.indexOf('item') !== -1) {
|
||||
funcName = `get${uppercaseFirstLetter(view.component)}`;
|
||||
} else if (view.type.indexOf('update') !== -1) {
|
||||
funcName = `update${uppercaseFirstLetter(view.component)}`;
|
||||
} else if (view.type.indexOf('create') !== -1) {
|
||||
func = buildCreateFunc(view);
|
||||
}
|
||||
return func;
|
||||
}
|
||||
|
||||
function buildCreateFunc(view: EndpointDef): string {
|
||||
let func: string = '';
|
||||
let funcName = `create${uppercaseFirstLetter(view.component)}`;
|
||||
let query: string = `INSERT INTO ${view.table} SET `;
|
||||
let requiredQueryValues: string = '';
|
||||
let optionalValuesCode: string = '';
|
||||
for (let i in view.columns) {
|
||||
let c: ColumnRef = view.columns[i];
|
||||
if (c.required) {
|
||||
if (!query.endsWith('SET ')) {
|
||||
query = query + `, `;
|
||||
}
|
||||
query = query + `${c.table}.${c.name} = ?`;
|
||||
if (requiredQueryValues.length) {
|
||||
requiredQueryValues = requiredQueryValues + `, `;
|
||||
}
|
||||
requiredQueryValues = requiredQueryValues + `params.${c.param}`;
|
||||
} else {
|
||||
optionalValuesCode = optionalValuesCode + `
|
||||
if (params.${c.param}) {
|
||||
query = query + ', ${c.table}.${c.name} = ?';
|
||||
queryValues.push(params.${c.param});
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 queryValues: string = '';
|
||||
let query: string = '';
|
||||
let tables: string = view.join?.length ? buildJoin(view) : `${view.table}`;
|
||||
let filters: string = '';
|
||||
funcName = `get${pluralize(uppercaseFirstLetter(view.component))}` + (view.type === 'count' ? 'Count' : '');
|
||||
|
||||
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 ')}`;
|
||||
}
|
||||
|
||||
query = `SELECT ${columns} FROM ${tables}${filters}`;
|
||||
let orderByCode = '';
|
||||
if (view.orderBy) {
|
||||
orderByCode = `
|
||||
query = query + '${buildOrderBy(view)}';
|
||||
`;
|
||||
}
|
||||
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}`;
|
||||
if (view.type === 'count') {
|
||||
funcParams = 'params: {[key: string]: string}';
|
||||
limitOrderCode = ``;
|
||||
}
|
||||
|
||||
func = `
|
||||
public ${funcName}(${funcParams}) {
|
||||
|
||||
let query: string = '${query}';
|
||||
let queryValues: string[] = [${requiredQueryValues}];
|
||||
|
||||
// optional params?
|
||||
${optionalFilterParts}
|
||||
${limitOrderCode}
|
||||
return super.runQuery(query, [...queryValues]);
|
||||
}
|
||||
`;
|
||||
return func;
|
||||
}
|
||||
|
||||
function buildJoin(view: EndpointDef): string {
|
||||
if (view.join?.length) {
|
||||
let join = `${view.table}`;
|
||||
for (let i in view.join) {
|
||||
let j: JoinDef = view.join[i];
|
||||
join = join + ` JOIN ${j.table} ON ${j.on.left.table}.${j.on.left.name} = ${j.on.right.table}.${j.on.right.name}`;
|
||||
}
|
||||
return join;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function buildOrderBy(view: EndpointDef): string {
|
||||
let orderBy: string = '';
|
||||
if (view.orderBy?.length) {
|
||||
orderBy = orderBy + ' ORDER BY ';
|
||||
for (let i in view.orderBy) {
|
||||
let order: Order = view.orderBy[i];
|
||||
if (parseInt(i) !== 0) {
|
||||
orderBy = orderBy + ', ';
|
||||
}
|
||||
orderBy = orderBy + `${order.column.table}.${order.column.name} ${order.direction}`;
|
||||
}
|
||||
}
|
||||
return orderBy;
|
||||
}
|
||||
|
||||
function tableHasColumn(tableName: string, column: string, sysDef: SystemDef): boolean {
|
||||
let answer: boolean = false;
|
||||
for (let i in sysDef.storage.tables) {
|
||||
let table: TableDef = sysDef.storage.tables[i];
|
||||
if (table.name === tableName) {
|
||||
for (let j in table.columns) {
|
||||
let c: ColumnDef = table.columns[j];
|
||||
if (column === c.name) {
|
||||
answer = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!answer && table.relations.length) {
|
||||
for (let k in table.columns) {
|
||||
let r: BelongsToDef = table.relations[k];
|
||||
if (column === `${r.table}_id`) {
|
||||
answer = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (answer) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return answer;
|
||||
}
|
||||
|
||||
function uppercaseFirstLetter(input: string)
|
||||
{
|
||||
return input.charAt(0).toUpperCase() + input.slice(1);
|
||||
|
37
ui-component-plan.md
Normal file
37
ui-component-plan.md
Normal file
@ -0,0 +1,37 @@
|
||||
# System Builder
|
||||
|
||||
System builder is a full stack builder for apps that allows you to define your data backend, APIs and views of the data and design your own apps or webpages
|
||||
|
||||
|
||||
### UI Components
|
||||
|
||||
All UI components need to be able to link into any data/api that has been fetched.
|
||||
|
||||
* Text
|
||||
* Image
|
||||
* Input (all types) plus Date Picker and Dropdown, checkbox, radio, text, textarea, email, password
|
||||
* Markdown editor: https://simplemde.com/ plus https://github.com/showdownjs/showdown
|
||||
* Table (with actions per item, maybe buttons on the list item)
|
||||
* List (with actions per item, maybe buttons on the list item)
|
||||
* Button (with text and actions)
|
||||
* charts?
|
||||
* div? <- probably just a 'section' that can contain other components
|
||||
* Loading style/animation?
|
||||
|
||||
#### Things to keep track of
|
||||
|
||||
There will have to be stuff we keep track of in order to reproduce the page as the user created it.
|
||||
|
||||
* All styles -> possibly apply css dynamically this way: https://stackoverflow.com/questions/1720320/how-to-dynamically-create-css-class-in-javascript-and-apply
|
||||
* Location in the document (could be hard coded but makes it hard for responsive design)
|
||||
* class and id names for reference in styles and events
|
||||
* accessibility text and other accessibility info
|
||||
|
||||
#### Other things
|
||||
|
||||
* Upload images to use in styles for backgrounds or elements
|
||||
* Save text and such in the database separate from the data source
|
||||
|
||||
#### How data sources will work
|
||||
|
||||
Many data sources can be defined or connected, but the default will be the app that is created in the system builder UI. Each view gets created with a unique name. All components can pull data from a view by referencing the view's name plus property or index. Data sources will all be pulled using the RXJS pattern used in bx-console and referenced by their unique names. Update calls will allow you to define a list of get requests to refresh after the update calls are made. Each view will have some state stored on it in addition to the data, including loading, and error state/messages.
|
10
yarn.lock
10
yarn.lock
@ -7,6 +7,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.13.tgz#ee1128e881b874c371374c1f72201893616417c9"
|
||||
integrity sha512-rouEWBImiRaSJsVA+ITTFM6ZxibuAlTuNOCyxVbwreu6k6+ujs7DfnU9o+PShFhET78pMBl3eH+AGSI5eOTkPA==
|
||||
|
||||
"@types/pluralize@^0.0.29":
|
||||
version "0.0.29"
|
||||
resolved "https://registry.yarnpkg.com/@types/pluralize/-/pluralize-0.0.29.tgz#6ffa33ed1fc8813c469b859681d09707eb40d03c"
|
||||
integrity sha512-BYOID+l2Aco2nBik+iYS4SZX0Lf20KPILP5RGmM1IgzdwNdTs0eebiFriOPcej1sX9mLnSoiNte5zcFxssgpGA==
|
||||
|
||||
inherits@2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||
@ -25,6 +30,11 @@ path@^0.12.7:
|
||||
process "^0.11.1"
|
||||
util "^0.10.3"
|
||||
|
||||
pluralize@^8.0.0:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1"
|
||||
integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==
|
||||
|
||||
process@^0.11.1:
|
||||
version "0.11.10"
|
||||
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
|
||||
|
Reference in New Issue
Block a user