add mapper builders
still needs all mapper versions but has list, create, and count working
This commit is contained in:
@ -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,59 +86,197 @@ let def: SystemDef = {
|
||||
}
|
||||
]
|
||||
},
|
||||
views: [
|
||||
components: [
|
||||
{
|
||||
component: 'task',
|
||||
type: ['list', 'count'],
|
||||
columns: [
|
||||
'name',
|
||||
'description',
|
||||
'completed',
|
||||
'completed_date',
|
||||
],
|
||||
orderBy: [
|
||||
endpoints: [
|
||||
{
|
||||
column: 'modified',
|
||||
direction: 'desc'
|
||||
component: 'task',
|
||||
table: 'task',
|
||||
type: 'list',
|
||||
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',
|
||||
column: {
|
||||
name: 'list_id',
|
||||
table: 'task'
|
||||
},
|
||||
comparison: '=',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
param: 'completed',
|
||||
column: {
|
||||
name: 'completed',
|
||||
table: 'task'
|
||||
},
|
||||
comparison: '=',
|
||||
required: false
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
component: 'task',
|
||||
table: 'task',
|
||||
type: 'count',
|
||||
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',
|
||||
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'
|
||||
}
|
||||
],
|
||||
}
|
||||
],
|
||||
filters: [
|
||||
{
|
||||
param: 'list',
|
||||
column: 'list_id',
|
||||
comparison: '=',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
param: 'completed',
|
||||
column: 'completed',
|
||||
comparison: '=',
|
||||
required: false
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
component: 'list',
|
||||
type: ['list', 'count'],
|
||||
columns: [
|
||||
'name',
|
||||
],
|
||||
orderBy: [
|
||||
endpoints: [
|
||||
{
|
||||
column: 'modified',
|
||||
direction: 'desc'
|
||||
}
|
||||
],
|
||||
filters: [
|
||||
{
|
||||
param: 'user',
|
||||
column: 'user_id',
|
||||
comparison: '=',
|
||||
required: true
|
||||
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: {
|
||||
name: 'user_id',
|
||||
table: 'list_user'
|
||||
},
|
||||
comparison: '=',
|
||||
required: true
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
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);
|
||||
|
Reference in New Issue
Block a user