@ruiapp/rapid-core 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bootstrapApplicationConfig.d.ts +3 -0
- package/dist/core/eventManager.d.ts +7 -0
- package/dist/core/http-types.d.ts +3 -0
- package/dist/core/httpHandler.d.ts +18 -0
- package/dist/core/plugin.d.ts +6 -0
- package/dist/core/pluginManager.d.ts +27 -0
- package/dist/core/request.d.ts +15 -0
- package/dist/core/response.d.ts +17 -0
- package/dist/core/routeContext.d.ts +17 -0
- package/dist/core/routesBuilder.d.ts +4 -0
- package/dist/core/server.d.ts +83 -0
- package/dist/dataAccess/dataAccessor.d.ts +20 -0
- package/dist/dataAccess/entityManager.d.ts +6 -0
- package/dist/dataAccess/entityMapper.d.ts +3 -0
- package/dist/dataAccess/filterHelper.d.ts +2 -0
- package/dist/dataAccess/propertyMapper.d.ts +3 -0
- package/dist/deno-std/assert/assert.d.ts +2 -0
- package/dist/deno-std/assert/assertion_error.d.ts +4 -0
- package/dist/deno-std/datetime/to_imf.d.ts +17 -0
- package/dist/deno-std/http/cookie.d.ts +134 -0
- package/dist/helpers/entityHelpers.d.ts +1 -0
- package/dist/helpers/inputHelper.d.ts +1 -0
- package/dist/helpers/runCollectionEntityHttpHandler.d.ts +5 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +3590 -0
- package/dist/plugins/authManager/httpHandlers/createSession.d.ts +8 -0
- package/dist/plugins/authManager/httpHandlers/deleteSession.d.ts +4 -0
- package/dist/plugins/authManager/httpHandlers/getMyProfile.d.ts +4 -0
- package/dist/plugins/authManager/httpHandlers/index.d.ts +5 -0
- package/dist/plugins/authManager/mod.d.ts +16 -0
- package/dist/plugins/authManager/models/AccessToken.d.ts +3 -0
- package/dist/plugins/authManager/models/index.d.ts +2 -0
- package/dist/plugins/authManager/routes/getMyProfile.d.ts +3 -0
- package/dist/plugins/authManager/routes/index.d.ts +2 -0
- package/dist/plugins/authManager/routes/signin.d.ts +3 -0
- package/dist/plugins/authManager/routes/signout.d.ts +3 -0
- package/dist/plugins/dataManager/httpHandlers/addEntityRelations.d.ts +4 -0
- package/dist/plugins/dataManager/httpHandlers/countCollectionEntities.d.ts +4 -0
- package/dist/plugins/dataManager/httpHandlers/createCollectionEntitiesBatch.d.ts +4 -0
- package/dist/plugins/dataManager/httpHandlers/createCollectionEntity.d.ts +4 -0
- package/dist/plugins/dataManager/httpHandlers/deleteCollectionEntityById.d.ts +4 -0
- package/dist/plugins/dataManager/httpHandlers/findCollectionEntities.d.ts +4 -0
- package/dist/plugins/dataManager/httpHandlers/findCollectionEntityById.d.ts +4 -0
- package/dist/plugins/dataManager/httpHandlers/queryDatabase.d.ts +4 -0
- package/dist/plugins/dataManager/httpHandlers/removeEntityRelations.d.ts +4 -0
- package/dist/plugins/dataManager/httpHandlers/updateCollectionEntityById.d.ts +4 -0
- package/dist/plugins/dataManager/mod.d.ts +16 -0
- package/dist/plugins/metaManager/httpHandlers/getMetaModelDetail.d.ts +4 -0
- package/dist/plugins/metaManager/httpHandlers/listMetaModels.d.ts +4 -0
- package/dist/plugins/metaManager/mod.d.ts +15 -0
- package/dist/plugins/routeManager/httpHandlers/httpProxy.d.ts +4 -0
- package/dist/plugins/routeManager/httpHandlers/listMetaRoutes.d.ts +4 -0
- package/dist/plugins/routeManager/mod.d.ts +15 -0
- package/dist/plugins/webhooks/mod.d.ts +24 -0
- package/dist/plugins/webhooks/pluginConfig.d.ts +48 -0
- package/dist/polyfill.d.ts +1 -0
- package/dist/proxy/mod.d.ts +13 -0
- package/dist/proxy/types.d.ts +17 -0
- package/dist/queryBuilder/index.d.ts +1 -0
- package/dist/queryBuilder/queryBuilder.d.ts +34 -0
- package/dist/server.d.ts +31 -0
- package/dist/types.d.ts +327 -0
- package/dist/utilities/httpUtility.d.ts +1 -0
- package/dist/utilities/jwtUtility.d.ts +8 -0
- package/dist/utilities/rapidUtility.d.ts +2 -0
- package/dist/utilities/typeUtility.d.ts +3 -0
- package/package.json +29 -0
- package/rollup.config.js +20 -0
- package/src/bootstrapApplicationConfig.ts +524 -0
- package/src/core/eventManager.ts +21 -0
- package/src/core/http-types.ts +4 -0
- package/src/core/httpHandler.ts +29 -0
- package/src/core/plugin.ts +13 -0
- package/src/core/pluginManager.ts +143 -0
- package/src/core/request.ts +23 -0
- package/src/core/response.ts +77 -0
- package/src/core/routeContext.ts +38 -0
- package/src/core/routesBuilder.ts +86 -0
- package/src/core/server.ts +144 -0
- package/src/dataAccess/dataAccessor.ts +110 -0
- package/src/dataAccess/entityManager.ts +651 -0
- package/src/dataAccess/entityMapper.ts +74 -0
- package/src/dataAccess/filterHelper.ts +47 -0
- package/src/dataAccess/propertyMapper.ts +27 -0
- package/src/deno-std/assert/assert.ts +9 -0
- package/src/deno-std/assert/assertion_error.ts +7 -0
- package/src/deno-std/datetime/to_imf.ts +47 -0
- package/src/deno-std/http/cookie.ts +398 -0
- package/src/helpers/entityHelpers.ts +24 -0
- package/src/helpers/inputHelper.ts +11 -0
- package/src/helpers/runCollectionEntityHttpHandler.ts +34 -0
- package/src/index.ts +12 -0
- package/src/plugins/authManager/httpHandlers/createSession.ts +57 -0
- package/src/plugins/authManager/httpHandlers/deleteSession.ts +22 -0
- package/src/plugins/authManager/httpHandlers/getMyProfile.ts +43 -0
- package/src/plugins/authManager/httpHandlers/index.ts +10 -0
- package/src/plugins/authManager/mod.ts +56 -0
- package/src/plugins/authManager/models/AccessToken.ts +56 -0
- package/src/plugins/authManager/models/index.ts +5 -0
- package/src/plugins/authManager/routes/getMyProfile.ts +15 -0
- package/src/plugins/authManager/routes/index.ts +9 -0
- package/src/plugins/authManager/routes/signin.ts +15 -0
- package/src/plugins/authManager/routes/signout.ts +15 -0
- package/src/plugins/dataManager/httpHandlers/addEntityRelations.ts +76 -0
- package/src/plugins/dataManager/httpHandlers/countCollectionEntities.ts +22 -0
- package/src/plugins/dataManager/httpHandlers/createCollectionEntitiesBatch.ts +57 -0
- package/src/plugins/dataManager/httpHandlers/createCollectionEntity.ts +43 -0
- package/src/plugins/dataManager/httpHandlers/deleteCollectionEntityById.ts +38 -0
- package/src/plugins/dataManager/httpHandlers/findCollectionEntities.ts +35 -0
- package/src/plugins/dataManager/httpHandlers/findCollectionEntityById.ts +30 -0
- package/src/plugins/dataManager/httpHandlers/queryDatabase.ts +29 -0
- package/src/plugins/dataManager/httpHandlers/removeEntityRelations.ts +72 -0
- package/src/plugins/dataManager/httpHandlers/updateCollectionEntityById.ts +53 -0
- package/src/plugins/dataManager/mod.ts +150 -0
- package/src/plugins/metaManager/httpHandlers/getMetaModelDetail.ts +14 -0
- package/src/plugins/metaManager/httpHandlers/listMetaModels.ts +13 -0
- package/src/plugins/metaManager/mod.ts +419 -0
- package/src/plugins/routeManager/httpHandlers/httpProxy.ts +15 -0
- package/src/plugins/routeManager/httpHandlers/listMetaRoutes.ts +13 -0
- package/src/plugins/routeManager/mod.ts +97 -0
- package/src/plugins/webhooks/mod.ts +144 -0
- package/src/plugins/webhooks/pluginConfig.ts +74 -0
- package/src/polyfill.ts +5 -0
- package/src/proxy/mod.ts +47 -0
- package/src/proxy/types.ts +21 -0
- package/src/queryBuilder/index.ts +1 -0
- package/src/queryBuilder/queryBuilder.ts +424 -0
- package/src/server.ts +192 -0
- package/src/types.ts +438 -0
- package/src/utilities/httpUtility.ts +23 -0
- package/src/utilities/jwtUtility.ts +16 -0
- package/src/utilities/rapidUtility.ts +5 -0
- package/src/utilities/typeUtility.ts +11 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meta manager plugin
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as _ from "lodash";
|
|
6
|
+
import { findEntities } from "~/dataAccess/entityManager";
|
|
7
|
+
import {
|
|
8
|
+
IPluginInstance,
|
|
9
|
+
IQueryBuilder,
|
|
10
|
+
QuoteTableOptions,
|
|
11
|
+
RpdApplicationConfig,
|
|
12
|
+
RpdDataModel,
|
|
13
|
+
RpdDataModelProperty,
|
|
14
|
+
RpdDataPropertyTypes,
|
|
15
|
+
RpdEntityCreateEventPayload,
|
|
16
|
+
RpdEntityDeleteEventPayload,
|
|
17
|
+
RpdEntityUpdateEventPayload,
|
|
18
|
+
} from "~/types";
|
|
19
|
+
import { IRpdServer, RpdConfigurationItemOptions, RpdServerPluginConfigurableTargetOptions, RpdServerPluginExtendingAbilities } from "~/core/server";
|
|
20
|
+
|
|
21
|
+
import * as listMetaModels from "./httpHandlers/listMetaModels";
|
|
22
|
+
import * as getMetaModelDetail from "./httpHandlers/getMetaModelDetail";
|
|
23
|
+
import { isRelationProperty } from "~/utilities/rapidUtility";
|
|
24
|
+
|
|
25
|
+
export const code = "metaManager";
|
|
26
|
+
export const description = "metaManager";
|
|
27
|
+
export const extendingAbilities: RpdServerPluginExtendingAbilities[] = [];
|
|
28
|
+
export const configurableTargets: RpdServerPluginConfigurableTargetOptions[] = [];
|
|
29
|
+
export const configurations: RpdConfigurationItemOptions[] = [];
|
|
30
|
+
|
|
31
|
+
let _plugin: IPluginInstance;
|
|
32
|
+
|
|
33
|
+
export async function initPlugin(plugin: IPluginInstance, server: IRpdServer) {
|
|
34
|
+
_plugin = plugin;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function registerHttpHandlers(server: IRpdServer) {
|
|
38
|
+
server.registerHttpHandler(_plugin, listMetaModels);
|
|
39
|
+
server.registerHttpHandler(_plugin, getMetaModelDetail);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function registerEventHandlers(server: IRpdServer) {
|
|
43
|
+
server.registerEventHandler(
|
|
44
|
+
"entity.create",
|
|
45
|
+
handleEntityCreateEvent.bind(null, server),
|
|
46
|
+
);
|
|
47
|
+
server.registerEventHandler(
|
|
48
|
+
"entity.update",
|
|
49
|
+
handleEntityUpdateEvent.bind(null, server),
|
|
50
|
+
);
|
|
51
|
+
server.registerEventHandler(
|
|
52
|
+
"entity.delete",
|
|
53
|
+
handleEntityDeleteEvent.bind(null, server),
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function handleEntityCreateEvent(
|
|
58
|
+
server: IRpdServer,
|
|
59
|
+
sender: IPluginInstance,
|
|
60
|
+
payload: RpdEntityCreateEventPayload,
|
|
61
|
+
) {
|
|
62
|
+
if (sender === _plugin) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (payload.namespace === "meta" && payload.modelSingularCode === "model") {
|
|
67
|
+
return;
|
|
68
|
+
const { queryBuilder } = server;
|
|
69
|
+
const model: Partial<RpdDataModel> = payload.after;
|
|
70
|
+
if (model.tableName) {
|
|
71
|
+
const model: RpdDataModel = payload.after;
|
|
72
|
+
await server.queryDatabaseObject(
|
|
73
|
+
`CREATE TABLE ${queryBuilder.quoteTable(model)} ();`,
|
|
74
|
+
[],
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function handleEntityUpdateEvent(
|
|
81
|
+
server: IRpdServer,
|
|
82
|
+
sender: IPluginInstance,
|
|
83
|
+
payload: RpdEntityUpdateEventPayload,
|
|
84
|
+
) {
|
|
85
|
+
if (sender === _plugin) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (payload.namespace === "meta" && payload.modelSingularCode === "model") {
|
|
90
|
+
return;
|
|
91
|
+
const { queryBuilder } = server;
|
|
92
|
+
const modelChanges: Partial<RpdDataModel> = payload.changes;
|
|
93
|
+
if (modelChanges.tableName) {
|
|
94
|
+
const modelBefore: RpdDataModel = payload.before;
|
|
95
|
+
await server.queryDatabaseObject(
|
|
96
|
+
`ALTER TABLE ${queryBuilder.quoteTable(modelBefore)} RENAME TO ${queryBuilder.quoteTable(modelChanges as QuoteTableOptions)}`,
|
|
97
|
+
[],
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function handleEntityDeleteEvent(
|
|
104
|
+
server: IRpdServer,
|
|
105
|
+
sender: IPluginInstance,
|
|
106
|
+
payload: RpdEntityDeleteEventPayload,
|
|
107
|
+
) {
|
|
108
|
+
if (sender === _plugin) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (payload.namespace !== "meta") {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const { queryBuilder } = server;
|
|
117
|
+
|
|
118
|
+
if (payload.modelSingularCode === "model") {
|
|
119
|
+
const deletedModel: RpdDataModel = payload.before;
|
|
120
|
+
await server.queryDatabaseObject(
|
|
121
|
+
`DROP TABLE ${queryBuilder.quoteTable(deletedModel)}`,
|
|
122
|
+
[],
|
|
123
|
+
);
|
|
124
|
+
} else if (payload.modelSingularCode === "property") {
|
|
125
|
+
const deletedProperty: RpdDataModelProperty = payload.before;
|
|
126
|
+
|
|
127
|
+
let columnNameToDrop = deletedProperty.columnName || deletedProperty.code;
|
|
128
|
+
if (isRelationProperty(deletedProperty)) {
|
|
129
|
+
if (deletedProperty.relation === "one") {
|
|
130
|
+
columnNameToDrop = deletedProperty.targetIdColumnName || "";
|
|
131
|
+
} else {
|
|
132
|
+
// many relation
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const dataAccessor = server.getDataAccessor<RpdDataModel>({
|
|
138
|
+
namespace: "meta",
|
|
139
|
+
singularCode: "model",
|
|
140
|
+
});
|
|
141
|
+
const model = await dataAccessor.findById((deletedProperty as any).modelId);
|
|
142
|
+
if (model) {
|
|
143
|
+
await server.queryDatabaseObject(
|
|
144
|
+
`ALTER TABLE ${queryBuilder.quoteTable(model)} DROP COLUMN ${
|
|
145
|
+
queryBuilder.quoteObject(columnNameToDrop)
|
|
146
|
+
}`,
|
|
147
|
+
[],
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function configureModels(
|
|
154
|
+
server: IRpdServer,
|
|
155
|
+
applicationConfig: RpdApplicationConfig,
|
|
156
|
+
) {
|
|
157
|
+
try {
|
|
158
|
+
const models = await listCollections(server, applicationConfig);
|
|
159
|
+
applicationConfig.models.push(...models);
|
|
160
|
+
} catch (ex) {
|
|
161
|
+
console.warn("Failed to loading existing meta of models.", ex.message);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function listCollections(
|
|
166
|
+
server: IRpdServer,
|
|
167
|
+
applicationConfig: RpdApplicationConfig,
|
|
168
|
+
) {
|
|
169
|
+
const dataAccessor = server.getDataAccessor({
|
|
170
|
+
namespace: "meta",
|
|
171
|
+
singularCode: "model",
|
|
172
|
+
});
|
|
173
|
+
const model = dataAccessor.getModel();
|
|
174
|
+
|
|
175
|
+
return findEntities(server, dataAccessor, {
|
|
176
|
+
properties: model.properties.map((item) => item.code),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function onApplicationLoaded(
|
|
181
|
+
server: IRpdServer,
|
|
182
|
+
applicationConfig: RpdApplicationConfig,
|
|
183
|
+
) {
|
|
184
|
+
console.log("metaManager.onApplicationLoaded");
|
|
185
|
+
await syncDatabaseSchema(server, applicationConfig);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
type TableInformation = {
|
|
189
|
+
table_schema: string;
|
|
190
|
+
table_name: string;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
type ColumnInformation = {
|
|
194
|
+
table_schema: string;
|
|
195
|
+
table_name: string;
|
|
196
|
+
column_name: string;
|
|
197
|
+
data_type: string;
|
|
198
|
+
udt_name: string;
|
|
199
|
+
is_nullable: "YES" | "NO";
|
|
200
|
+
column_default: string;
|
|
201
|
+
character_maximum_length: number;
|
|
202
|
+
numeric_precision: number;
|
|
203
|
+
numeric_scale: number;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function syncDatabaseSchema(
|
|
207
|
+
server: IRpdServer,
|
|
208
|
+
applicationConfig: RpdApplicationConfig,
|
|
209
|
+
) {
|
|
210
|
+
console.log("Synchronizing database schema...");
|
|
211
|
+
const sqlQueryTableInformations = `SELECT table_schema, table_name FROM information_schema.tables`;
|
|
212
|
+
const tablesInDb: TableInformation[] = await server.queryDatabaseObject(sqlQueryTableInformations);
|
|
213
|
+
const { queryBuilder } = server;
|
|
214
|
+
|
|
215
|
+
for (const model of applicationConfig.models) {
|
|
216
|
+
console.debug(`Checking data table for '${model.namespace}.${model.singularCode}'...`);
|
|
217
|
+
|
|
218
|
+
const expectedTableSchema = model.schema || server.databaseConfig.dbDefaultSchema;
|
|
219
|
+
const expectedTableName = model.tableName;
|
|
220
|
+
const tableInDb = _.find(tablesInDb, { table_schema: expectedTableSchema, table_name: expectedTableName});
|
|
221
|
+
if (!tableInDb) {
|
|
222
|
+
await server.queryDatabaseObject(`CREATE TABLE IF NOT EXISTS ${queryBuilder.quoteTable(model)} ()`, []);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const sqlQueryColumnInformations = `SELECT table_schema, table_name, column_name, data_type, udt_name, is_nullable, column_default, character_maximum_length, numeric_precision, numeric_scale
|
|
227
|
+
FROM information_schema.columns;`;
|
|
228
|
+
const columnsInDb: ColumnInformation[] = await server.queryDatabaseObject(sqlQueryColumnInformations, []);
|
|
229
|
+
|
|
230
|
+
for (const model of applicationConfig.models) {
|
|
231
|
+
console.debug(`Checking data columns for '${model.namespace}.${model.singularCode}'...`);
|
|
232
|
+
|
|
233
|
+
for (const property of model.properties) {
|
|
234
|
+
let columnDDL;
|
|
235
|
+
if (isRelationProperty(property)) {
|
|
236
|
+
if (property.relation === "one") {
|
|
237
|
+
const targetModel = applicationConfig.models.find(item => item.singularCode === property.targetSingularCode);
|
|
238
|
+
if (!targetModel) {
|
|
239
|
+
console.warn(`Cannot find target model with singular code "${property.targetSingularCode}".`)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const columnInDb: ColumnInformation | undefined = _.find(columnsInDb, {
|
|
243
|
+
table_schema: model.schema || "public",
|
|
244
|
+
table_name: model.tableName,
|
|
245
|
+
column_name: property.targetIdColumnName!,
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
if (!columnInDb) {
|
|
249
|
+
columnDDL = generateCreateColumnDDL(queryBuilder, {
|
|
250
|
+
schema: model.schema,
|
|
251
|
+
tableName: model.tableName,
|
|
252
|
+
name: property.targetIdColumnName!,
|
|
253
|
+
type: "integer",
|
|
254
|
+
autoIncrement: false,
|
|
255
|
+
notNull: property.required,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
} else if (property.relation === "many") {
|
|
259
|
+
if (property.linkTableName) {
|
|
260
|
+
const tableInDb = _.find(tablesInDb, { table_schema: property.linkSchema || server.databaseConfig.dbDefaultSchema, table_name: property.linkTableName});
|
|
261
|
+
if (!tableInDb) {
|
|
262
|
+
columnDDL = generateLinkTableDDL(queryBuilder, {
|
|
263
|
+
linkSchema: property.linkSchema,
|
|
264
|
+
linkTableName: property.linkTableName,
|
|
265
|
+
targetIdColumnName: property.targetIdColumnName!,
|
|
266
|
+
selfIdColumnName: property.selfIdColumnName!,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
const targetModel = applicationConfig.models.find(item => item.singularCode === property.targetSingularCode);
|
|
271
|
+
if (!targetModel) {
|
|
272
|
+
console.warn(`Cannot find target model with singular code "${property.targetSingularCode}".`)
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const columnInDb: ColumnInformation | undefined = _.find(columnsInDb, {
|
|
277
|
+
table_schema: targetModel.schema || "public",
|
|
278
|
+
table_name: targetModel.tableName,
|
|
279
|
+
column_name: property.selfIdColumnName!,
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
if (!columnInDb) {
|
|
283
|
+
columnDDL = generateCreateColumnDDL(queryBuilder, {
|
|
284
|
+
schema: targetModel.schema,
|
|
285
|
+
tableName: targetModel.tableName,
|
|
286
|
+
name: property.selfIdColumnName || "",
|
|
287
|
+
type: "integer",
|
|
288
|
+
autoIncrement: false,
|
|
289
|
+
notNull: property.required,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
} else {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (columnDDL) {
|
|
298
|
+
await server.tryQueryDatabaseObject(columnDDL);
|
|
299
|
+
}
|
|
300
|
+
} else {
|
|
301
|
+
const columnName = property.columnName || property.code;
|
|
302
|
+
const columnInDb: ColumnInformation | undefined = _.find(columnsInDb, {
|
|
303
|
+
table_schema: model.schema || "public",
|
|
304
|
+
table_name: model.tableName,
|
|
305
|
+
column_name: columnName,
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
if (!columnInDb) {
|
|
309
|
+
// create column if not exists
|
|
310
|
+
columnDDL = generateCreateColumnDDL(queryBuilder, {
|
|
311
|
+
schema: model.schema,
|
|
312
|
+
tableName: model.tableName,
|
|
313
|
+
name: columnName,
|
|
314
|
+
type: property.type,
|
|
315
|
+
autoIncrement: property.autoIncrement,
|
|
316
|
+
notNull: property.required,
|
|
317
|
+
defaultValue: property.defaultValue,
|
|
318
|
+
});
|
|
319
|
+
await server.tryQueryDatabaseObject(columnDDL);
|
|
320
|
+
} else {
|
|
321
|
+
const expectedColumnType = pgPropertyTypeColumnMap[property.type];
|
|
322
|
+
if (columnInDb.udt_name !== expectedColumnType) {
|
|
323
|
+
const sqlAlterColumnType = `alter table ${queryBuilder.quoteTable(model)} alter column ${queryBuilder.quoteObject(columnName)} type ${expectedColumnType}`;
|
|
324
|
+
await server.tryQueryDatabaseObject(sqlAlterColumnType);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (property.defaultValue) {
|
|
328
|
+
if (!columnInDb.column_default) {
|
|
329
|
+
const sqlSetColumnDefault = `alter table ${queryBuilder.quoteTable(model)} alter column ${queryBuilder.quoteObject(columnName)} set default ${property.defaultValue}`;
|
|
330
|
+
await server.tryQueryDatabaseObject(sqlSetColumnDefault);
|
|
331
|
+
}
|
|
332
|
+
} else {
|
|
333
|
+
if (columnInDb.column_default && !property.autoIncrement) {
|
|
334
|
+
const sqlDropColumnDefault = `alter table ${queryBuilder.quoteTable(model)} alter column ${queryBuilder.quoteObject(columnName)} drop default`;
|
|
335
|
+
await server.tryQueryDatabaseObject(sqlDropColumnDefault);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (property.required) {
|
|
340
|
+
if (columnInDb.is_nullable === "YES") {
|
|
341
|
+
const sqlSetColumnNotNull = `alter table ${queryBuilder.quoteTable(model)} alter column ${queryBuilder.quoteObject(columnName)} set not null`;
|
|
342
|
+
await server.tryQueryDatabaseObject(sqlSetColumnNotNull);
|
|
343
|
+
}
|
|
344
|
+
} else {
|
|
345
|
+
if (columnInDb.is_nullable === "NO") {
|
|
346
|
+
const sqlDropColumnNotNull = `alter table ${queryBuilder.quoteTable(model)} alter column ${queryBuilder.quoteObject(columnName)} drop not null`;
|
|
347
|
+
await server.tryQueryDatabaseObject(sqlDropColumnNotNull);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function generateCreateColumnDDL(queryBuilder: IQueryBuilder, options: {
|
|
357
|
+
schema?: string;
|
|
358
|
+
tableName: string;
|
|
359
|
+
name: string;
|
|
360
|
+
type: RpdDataPropertyTypes;
|
|
361
|
+
autoIncrement?: boolean;
|
|
362
|
+
notNull?: boolean;
|
|
363
|
+
defaultValue?: string;
|
|
364
|
+
}) {
|
|
365
|
+
let columnDDL = `ALTER TABLE ${queryBuilder.quoteTable(options)} ADD`;
|
|
366
|
+
columnDDL += ` ${queryBuilder.quoteObject(options.name)}`;
|
|
367
|
+
if (options.type === "integer" && options.autoIncrement) {
|
|
368
|
+
columnDDL += ` serial`;
|
|
369
|
+
} else {
|
|
370
|
+
const columnType = pgPropertyTypeColumnMap[options.type];
|
|
371
|
+
if (!columnType) {
|
|
372
|
+
console.log('options', options);
|
|
373
|
+
throw new Error(`Property type "${options.type}" is not supported.`);
|
|
374
|
+
}
|
|
375
|
+
columnDDL += ` ${columnType}`;
|
|
376
|
+
}
|
|
377
|
+
if (options.notNull) {
|
|
378
|
+
columnDDL += " NOT NULL";
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (options.defaultValue) {
|
|
382
|
+
columnDDL += ` DEFAULT ${options.defaultValue}`;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return columnDDL;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
function generateLinkTableDDL(queryBuilder: IQueryBuilder, options: {
|
|
390
|
+
linkSchema?: string;
|
|
391
|
+
linkTableName: string;
|
|
392
|
+
targetIdColumnName: string;
|
|
393
|
+
selfIdColumnName: string;
|
|
394
|
+
}) {
|
|
395
|
+
let columnDDL = `CREATE TABLE ${queryBuilder.quoteTable({
|
|
396
|
+
schema: options.linkSchema,
|
|
397
|
+
tableName: options.linkTableName,
|
|
398
|
+
})} (`;
|
|
399
|
+
columnDDL += `id serial not null,`;
|
|
400
|
+
columnDDL += `${queryBuilder.quoteObject(options.selfIdColumnName)} integer not null,`;
|
|
401
|
+
columnDDL += `${queryBuilder.quoteObject(options.targetIdColumnName)} integer not null)`;
|
|
402
|
+
|
|
403
|
+
return columnDDL;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
const pgPropertyTypeColumnMap: Partial<Record<RpdDataPropertyTypes, string>> = {
|
|
408
|
+
integer: "int4",
|
|
409
|
+
long: "int8",
|
|
410
|
+
float: "float4",
|
|
411
|
+
double: "float8",
|
|
412
|
+
decimal: "decimal",
|
|
413
|
+
text: "text",
|
|
414
|
+
boolean: "bool",
|
|
415
|
+
date: "date",
|
|
416
|
+
datetime: "timestamptz",
|
|
417
|
+
json: "jsonb",
|
|
418
|
+
option: "text",
|
|
419
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { IPluginInstance, RunProxyHandlerOptions } from "~/types";
|
|
2
|
+
import { doProxy } from "~/proxy/mod";
|
|
3
|
+
import { HttpHandlerContext } from "~/core/httpHandler";
|
|
4
|
+
|
|
5
|
+
export const code = "httpProxy";
|
|
6
|
+
|
|
7
|
+
export async function handler(
|
|
8
|
+
plugin: IPluginInstance,
|
|
9
|
+
ctx: HttpHandlerContext,
|
|
10
|
+
options: RunProxyHandlerOptions,
|
|
11
|
+
) {
|
|
12
|
+
console.debug(`Running ${code} handler...`);
|
|
13
|
+
|
|
14
|
+
await doProxy(ctx.routerContext, options);
|
|
15
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { HttpHandlerContext } from "~/core/httpHandler";
|
|
2
|
+
import { IPluginInstance } from "~/types";
|
|
3
|
+
|
|
4
|
+
export const code = "listMetaRoutes";
|
|
5
|
+
|
|
6
|
+
export async function handler(
|
|
7
|
+
plugin: IPluginInstance,
|
|
8
|
+
ctx: HttpHandlerContext,
|
|
9
|
+
options: any,
|
|
10
|
+
) {
|
|
11
|
+
const { applicationConfig } = ctx;
|
|
12
|
+
ctx.output = { list: applicationConfig.routes };
|
|
13
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route manager plugin
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
IPluginInstance,
|
|
7
|
+
RpdApplicationConfig,
|
|
8
|
+
} from "~/types";
|
|
9
|
+
import { RpdServerPluginExtendingAbilities, RpdServerPluginConfigurableTargetOptions, RpdConfigurationItemOptions, IRpdServer } from "~/core/server";
|
|
10
|
+
import { findEntities } from "../../dataAccess/entityManager";
|
|
11
|
+
import * as httpProxy from "./httpHandlers/httpProxy";
|
|
12
|
+
import * as listMetaRoutes from "./httpHandlers/listMetaRoutes";
|
|
13
|
+
import { buildRoutes } from "~/core/routesBuilder";
|
|
14
|
+
|
|
15
|
+
export const code = "routeManager";
|
|
16
|
+
export const description = "routeManager";
|
|
17
|
+
export const extendingAbilities: RpdServerPluginExtendingAbilities[] = [];
|
|
18
|
+
export const configurableTargets: RpdServerPluginConfigurableTargetOptions[] = [];
|
|
19
|
+
export const configurations: RpdConfigurationItemOptions[] = [];
|
|
20
|
+
|
|
21
|
+
let _plugin: IPluginInstance;
|
|
22
|
+
|
|
23
|
+
export async function initPlugin(plugin: IPluginInstance, server: IRpdServer) {
|
|
24
|
+
_plugin = plugin;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let routes: any;
|
|
28
|
+
|
|
29
|
+
async function routeHandler(ctx: any, next: any) {
|
|
30
|
+
if (routes) {
|
|
31
|
+
await routes(ctx, next);
|
|
32
|
+
} else {
|
|
33
|
+
await next();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function registerMiddlewares(server: IRpdServer) {
|
|
38
|
+
// server.registerMiddleware(routeHandler);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// export async function registerEventHandlers(server: IRpdServer) {
|
|
42
|
+
// server.registerEventHandler("entity.create", handleEntityEvent.bind(null, server))
|
|
43
|
+
// server.registerEventHandler("entity.update", handleEntityEvent.bind(null, server))
|
|
44
|
+
// server.registerEventHandler("entity.delete", handleEntityEvent.bind(null, server))
|
|
45
|
+
// }
|
|
46
|
+
|
|
47
|
+
// async function handleEntityEvent(server: IRpdServer, sender: IPluginInstance, payload: RpdEntityCreateEventPayload | RpdEntityUpdateEventPayload | RpdEntityDeleteEventPayload) {
|
|
48
|
+
// if (sender === _plugin) {
|
|
49
|
+
// return;
|
|
50
|
+
// }
|
|
51
|
+
|
|
52
|
+
// if (payload.namespace === "meta" && payload.modelSingularCode === "route") {
|
|
53
|
+
// console.debug("Rebuilding routes...");
|
|
54
|
+
// routesRef.value = await buildRoutes(server, server.getApplicationConfig());
|
|
55
|
+
// }
|
|
56
|
+
// }
|
|
57
|
+
|
|
58
|
+
export async function registerHttpHandlers(server: IRpdServer) {
|
|
59
|
+
server.registerHttpHandler(_plugin, httpProxy);
|
|
60
|
+
server.registerHttpHandler(_plugin, listMetaRoutes);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function configureRoutes(
|
|
64
|
+
server: IRpdServer,
|
|
65
|
+
applicationConfig: RpdApplicationConfig,
|
|
66
|
+
) {
|
|
67
|
+
try {
|
|
68
|
+
const routes = await listRoutes(server, applicationConfig);
|
|
69
|
+
applicationConfig.routes.push(...routes);
|
|
70
|
+
} catch (ex) {
|
|
71
|
+
console.warn("Failed to loading existing meta of routes.", ex.message);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function listRoutes(
|
|
76
|
+
server: IRpdServer,
|
|
77
|
+
applicationConfig: RpdApplicationConfig,
|
|
78
|
+
) {
|
|
79
|
+
const dataAccessor = server.getDataAccessor({
|
|
80
|
+
namespace: "meta",
|
|
81
|
+
singularCode: "route",
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return findEntities(server, dataAccessor, {
|
|
85
|
+
orderBy: [
|
|
86
|
+
{ field: "endpoint" },
|
|
87
|
+
],
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function onApplicationLoaded(
|
|
92
|
+
server: IRpdServer,
|
|
93
|
+
applicationConfig: RpdApplicationConfig,
|
|
94
|
+
) {
|
|
95
|
+
console.log("[routeManager.onApplicationLoaded] build routes");
|
|
96
|
+
routes = await buildRoutes(server, applicationConfig);
|
|
97
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhooks plugin
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as _ from "lodash";
|
|
6
|
+
import {
|
|
7
|
+
IPluginInstance,
|
|
8
|
+
RpdApplicationConfig,
|
|
9
|
+
RpdEntityCreateEventPayload,
|
|
10
|
+
RpdEntityDeleteEventPayload,
|
|
11
|
+
RpdEntityUpdateEventPayload,
|
|
12
|
+
RpdServerEventTypes,
|
|
13
|
+
} from "~/types";
|
|
14
|
+
import { RpdServerPluginExtendingAbilities, RpdServerPluginConfigurableTargetOptions, RpdConfigurationItemOptions, IRpdServer } from "~/core/server";
|
|
15
|
+
import { fetchWithTimeout } from "~/utilities/httpUtility";
|
|
16
|
+
import { findEntities } from "~/dataAccess/entityManager";
|
|
17
|
+
import pluginConfig from "./pluginConfig";
|
|
18
|
+
|
|
19
|
+
export const code = "webhooks";
|
|
20
|
+
export const description = "webhooks";
|
|
21
|
+
export const extendingAbilities: RpdServerPluginExtendingAbilities[] = [];
|
|
22
|
+
export const configurableTargets: RpdServerPluginConfigurableTargetOptions[] = [];
|
|
23
|
+
export const configurations: RpdConfigurationItemOptions[] = [];
|
|
24
|
+
|
|
25
|
+
export interface Webhook {
|
|
26
|
+
name: string;
|
|
27
|
+
url: string;
|
|
28
|
+
secret: string;
|
|
29
|
+
namespace: string;
|
|
30
|
+
modelSingularCode: string;
|
|
31
|
+
events: string[];
|
|
32
|
+
properties: string[];
|
|
33
|
+
enabled: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let _plugin: IPluginInstance;
|
|
37
|
+
let webhooks: Webhook[];
|
|
38
|
+
|
|
39
|
+
export async function initPlugin(plugin: IPluginInstance, server: IRpdServer) {
|
|
40
|
+
_plugin = plugin;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function configureModels(
|
|
44
|
+
server: IRpdServer,
|
|
45
|
+
applicationConfig: RpdApplicationConfig,
|
|
46
|
+
) {
|
|
47
|
+
for (const model of pluginConfig.models) {
|
|
48
|
+
applicationConfig.models.push(model);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function registerEventHandlers(server: IRpdServer) {
|
|
53
|
+
const events: (keyof RpdServerEventTypes)[] = [
|
|
54
|
+
"entity.create",
|
|
55
|
+
"entity.update",
|
|
56
|
+
"entity.delete",
|
|
57
|
+
];
|
|
58
|
+
for (const event of events) {
|
|
59
|
+
server.registerEventHandler(
|
|
60
|
+
event,
|
|
61
|
+
handleEntityEvent.bind(null, server, event),
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function handleEntityEvent(
|
|
67
|
+
server: IRpdServer,
|
|
68
|
+
event: keyof RpdServerEventTypes,
|
|
69
|
+
sender: IPluginInstance,
|
|
70
|
+
payload:
|
|
71
|
+
| RpdEntityCreateEventPayload
|
|
72
|
+
| RpdEntityUpdateEventPayload
|
|
73
|
+
| RpdEntityDeleteEventPayload,
|
|
74
|
+
) {
|
|
75
|
+
if (sender === _plugin) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (payload.namespace === "sys" && payload.modelSingularCode === "webhook") {
|
|
80
|
+
webhooks = await listWebhooks(server);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// We will not trigger webhooks if entity changed is in "meta" or "sys" namespaces.
|
|
85
|
+
if (payload.namespace === "meta" || payload.namespace === "sys") {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const webhook of webhooks) {
|
|
90
|
+
if (_.indexOf(webhook.events, event) === -1) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (
|
|
95
|
+
webhook.namespace != payload.namespace ||
|
|
96
|
+
webhook.modelSingularCode !== payload.modelSingularCode
|
|
97
|
+
) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
console.debug(`Triggering webhook. ${webhook.url}`);
|
|
102
|
+
// TODO: It's better to trigger webhook through message queue.
|
|
103
|
+
try {
|
|
104
|
+
await fetchWithTimeout(webhook.url, {
|
|
105
|
+
method: "post",
|
|
106
|
+
headers: {
|
|
107
|
+
"Content-Type": "application/json",
|
|
108
|
+
"x-webhook-secret": webhook.secret || "",
|
|
109
|
+
},
|
|
110
|
+
body: JSON.stringify({ event, payload }),
|
|
111
|
+
});
|
|
112
|
+
} catch (err) {
|
|
113
|
+
console.warn(new Error("Failed to call webhook. " + err.message));
|
|
114
|
+
console.warn(err);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function onApplicationLoaded(
|
|
120
|
+
server: IRpdServer,
|
|
121
|
+
applicationConfig: RpdApplicationConfig,
|
|
122
|
+
) {
|
|
123
|
+
console.log("[webhooks.onApplicationLoaded] loading webhooks");
|
|
124
|
+
webhooks = await listWebhooks(server);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function listWebhooks(
|
|
128
|
+
server: IRpdServer,
|
|
129
|
+
) {
|
|
130
|
+
const dataAccessor = server.getDataAccessor({
|
|
131
|
+
namespace: "sys",
|
|
132
|
+
singularCode: "webhook",
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return findEntities(server, dataAccessor, {
|
|
136
|
+
filters: [
|
|
137
|
+
{
|
|
138
|
+
field: "enabled",
|
|
139
|
+
operator: "eq",
|
|
140
|
+
value: true,
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
});
|
|
144
|
+
}
|