core-express 0.1.0 → 0.1.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/README.md +1 -1
- package/lib/GenericController.js +224 -131
- package/lib/GenericSearchController.js +58 -37
- package/lib/HealthController.js +16 -58
- package/lib/LoadController.js +71 -46
- package/lib/LoadSearchController.js +113 -41
- package/lib/LogController.js +89 -0
- package/lib/LowCodeController.js +100 -0
- package/lib/SearchController.js +32 -25
- package/lib/access.js +34 -0
- package/lib/client.js +80 -0
- package/lib/edit.js +99 -84
- package/lib/health.js +157 -76
- package/lib/http.js +316 -0
- package/lib/index.js +488 -16
- package/lib/log.js +242 -0
- package/lib/resources.js +106 -6
- package/lib/search.js +766 -160
- package/lib/view.js +48 -34
- package/package.json +8 -4
- package/src/GenericController.ts +217 -106
- package/src/GenericSearchController.ts +38 -21
- package/src/HealthController.ts +9 -9
- package/src/LoadController.ts +70 -40
- package/src/LoadSearchController.ts +114 -21
- package/src/LogController.ts +137 -0
- package/src/LowCodeController.ts +88 -0
- package/src/SearchController.ts +28 -17
- package/src/access.ts +42 -0
- package/src/client.ts +69 -0
- package/src/edit.ts +93 -61
- package/src/health.ts +27 -28
- package/src/http.ts +295 -0
- package/src/index.ts +464 -13
- package/src/log.ts +245 -0
- package/src/metadata.ts +34 -23
- package/src/resources.ts +143 -4
- package/src/search.ts +782 -198
- package/src/view.ts +44 -30
- package/tsconfig.json +1 -0
- package/lib/response.js +0 -12
- package/src/response.ts +0 -10
package/src/log.ts
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { NextFunction, Request, Response } from "express"
|
|
2
|
+
import { PassThrough } from "stream"
|
|
3
|
+
import { resources } from "./resources"
|
|
4
|
+
|
|
5
|
+
export interface LogConf {
|
|
6
|
+
log?: boolean
|
|
7
|
+
separate?: boolean
|
|
8
|
+
skips?: string
|
|
9
|
+
request?: string
|
|
10
|
+
response?: string
|
|
11
|
+
duration?: string
|
|
12
|
+
status?: string
|
|
13
|
+
size?: string
|
|
14
|
+
}
|
|
15
|
+
export interface MiddleLog {
|
|
16
|
+
log?: boolean
|
|
17
|
+
separate?: boolean
|
|
18
|
+
skips: string[]
|
|
19
|
+
duration: string
|
|
20
|
+
request: string
|
|
21
|
+
response: string
|
|
22
|
+
status: string
|
|
23
|
+
size: string
|
|
24
|
+
}
|
|
25
|
+
export interface SimpleMap {
|
|
26
|
+
[key: string]: string | number | boolean | Date
|
|
27
|
+
}
|
|
28
|
+
export function createConfig(c?: LogConf): MiddleLog {
|
|
29
|
+
if (!c) {
|
|
30
|
+
return { skips: [], duration: "duration", request: "", response: "", status: "", size: "" }
|
|
31
|
+
}
|
|
32
|
+
const l: MiddleLog = {
|
|
33
|
+
log: c.log,
|
|
34
|
+
separate: c.separate,
|
|
35
|
+
skips: c.skips ? c.skips.split(",") : [],
|
|
36
|
+
duration: c.duration ? c.duration : "duration",
|
|
37
|
+
request: c.request ? c.request : "",
|
|
38
|
+
response: c.response ? c.response : "",
|
|
39
|
+
status: c.status ? c.status : "",
|
|
40
|
+
size: c.size ? c.size : "",
|
|
41
|
+
}
|
|
42
|
+
return l
|
|
43
|
+
}
|
|
44
|
+
export function skip(skips: string[], url: string): boolean {
|
|
45
|
+
if (skips.length === 0) {
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
const u = removeUrlParams(url)
|
|
49
|
+
for (const s of skips) {
|
|
50
|
+
if (u.endsWith(s)) {
|
|
51
|
+
return true
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return false
|
|
55
|
+
}
|
|
56
|
+
export function removeUrlParams(url: string): string {
|
|
57
|
+
const startParams = url.indexOf("?")
|
|
58
|
+
return startParams !== -1 ? url.substring(0, startParams) : url
|
|
59
|
+
}
|
|
60
|
+
export interface Middleware {
|
|
61
|
+
conf: MiddleLog
|
|
62
|
+
}
|
|
63
|
+
const o = "OPTIONS"
|
|
64
|
+
export class MiddlewareLogger {
|
|
65
|
+
constructor(public write: (msg: string, m?: SimpleMap) => void, conf?: LogConf, public build?: (req: Request, m: SimpleMap) => SimpleMap) {
|
|
66
|
+
this.log = this.log.bind(this)
|
|
67
|
+
this.conf = createConfig(conf)
|
|
68
|
+
}
|
|
69
|
+
conf: MiddleLog
|
|
70
|
+
log(req: Request, res: Response, next: NextFunction) {
|
|
71
|
+
const m = req.method
|
|
72
|
+
if (m !== o && this.conf.log && !skip(this.conf.skips, req.originalUrl)) {
|
|
73
|
+
const start = process.hrtime()
|
|
74
|
+
const x = this.conf.request
|
|
75
|
+
let r = false
|
|
76
|
+
if (m !== "GET" && m !== "DELETE") {
|
|
77
|
+
r = true
|
|
78
|
+
}
|
|
79
|
+
const msg = `${m} ${req.originalUrl}`
|
|
80
|
+
if (this.conf.separate && r) {
|
|
81
|
+
if (this.conf.request.length > 0) {
|
|
82
|
+
const op: SimpleMap = {}
|
|
83
|
+
op[x] = JSON.stringify(req.body)
|
|
84
|
+
if (this.build) {
|
|
85
|
+
const op2 = this.build(req, op)
|
|
86
|
+
this.write(msg, op2)
|
|
87
|
+
} else {
|
|
88
|
+
this.write(msg, op)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const chunks: Uint8Array[] = []
|
|
93
|
+
mapResponseBody(res, chunks)
|
|
94
|
+
res.on("finish", () => {
|
|
95
|
+
const duration = getDurationInMilliseconds(start)
|
|
96
|
+
const op: SimpleMap = {}
|
|
97
|
+
if (r && !this.conf.separate && this.conf.request.length > 0) {
|
|
98
|
+
op[x] = JSON.stringify(req.body)
|
|
99
|
+
}
|
|
100
|
+
if (this.conf.response.length > 0) {
|
|
101
|
+
const rsBody = Buffer.concat(chunks).toString(resources.encoding)
|
|
102
|
+
op[this.conf.response] = rsBody
|
|
103
|
+
}
|
|
104
|
+
if (this.conf.status.length > 0) {
|
|
105
|
+
op[this.conf.status] = res.statusCode
|
|
106
|
+
}
|
|
107
|
+
if (this.conf.size.length > 0) {
|
|
108
|
+
if ("_contentLength" in res) {
|
|
109
|
+
op[this.conf.size] = (res as any)["_contentLength"]
|
|
110
|
+
} else if (res.hasHeader("content-length")) {
|
|
111
|
+
const l = res.getHeader("content-length")
|
|
112
|
+
if (typeof l === "number" || typeof l === "string") {
|
|
113
|
+
op[this.conf.size] = l
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
op[this.conf.duration] = duration
|
|
118
|
+
if (this.build) {
|
|
119
|
+
const op2 = this.build(req, op)
|
|
120
|
+
this.write(msg, op2)
|
|
121
|
+
} else {
|
|
122
|
+
this.write(msg, op)
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
next()
|
|
126
|
+
} else {
|
|
127
|
+
next()
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const mapResponseBody = (res: Response, chunks: Uint8Array[]) => {
|
|
132
|
+
const defaultWrite = res.write.bind(res)
|
|
133
|
+
const defaultEnd = res.end.bind(res)
|
|
134
|
+
const ps = new PassThrough()
|
|
135
|
+
|
|
136
|
+
ps.on("data", (data: any) => chunks.push(data))
|
|
137
|
+
;(res as any).write = (...args: any) => {
|
|
138
|
+
;(ps as any).write(...args)
|
|
139
|
+
;(defaultWrite as any)(...args)
|
|
140
|
+
}
|
|
141
|
+
;(res as any).end = (...args: any) => {
|
|
142
|
+
ps.end(...args)
|
|
143
|
+
defaultEnd(...args)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const NS_PER_SEC = 1e9
|
|
147
|
+
const NS_TO_MS = 1e6
|
|
148
|
+
const getDurationInMilliseconds = (start: [number, number] | undefined) => {
|
|
149
|
+
const diff = process.hrtime(start)
|
|
150
|
+
return (diff[0] * NS_PER_SEC + diff[1]) / NS_TO_MS
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// tslint:disable-next-line:max-classes-per-file
|
|
154
|
+
export class MiddlewareController {
|
|
155
|
+
constructor(public logger: Middleware) {
|
|
156
|
+
this.config = this.config.bind(this)
|
|
157
|
+
}
|
|
158
|
+
config(req: Request, res: Response) {
|
|
159
|
+
const obj: MiddleLog = req.body
|
|
160
|
+
if (!this.logger) {
|
|
161
|
+
res.status(503).end("Logger is not available")
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
let changed = false
|
|
165
|
+
if (obj.log !== undefined) {
|
|
166
|
+
this.logger.conf.log = obj.log
|
|
167
|
+
changed = true
|
|
168
|
+
}
|
|
169
|
+
if (obj.separate !== undefined) {
|
|
170
|
+
this.logger.conf.separate = obj.separate
|
|
171
|
+
changed = true
|
|
172
|
+
}
|
|
173
|
+
if (Array.isArray(obj.skips)) {
|
|
174
|
+
if (isValidSkips(obj.skips)) {
|
|
175
|
+
this.logger.conf.skips = obj.skips
|
|
176
|
+
changed = true
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (typeof obj.duration === "string" && obj.duration.length > 0) {
|
|
180
|
+
this.logger.conf.duration = obj.duration
|
|
181
|
+
changed = true
|
|
182
|
+
}
|
|
183
|
+
if (typeof obj.request === "string") {
|
|
184
|
+
this.logger.conf.request = obj.request
|
|
185
|
+
changed = true
|
|
186
|
+
}
|
|
187
|
+
if (typeof obj.response === "string") {
|
|
188
|
+
this.logger.conf.response = obj.response
|
|
189
|
+
changed = true
|
|
190
|
+
}
|
|
191
|
+
if (typeof obj.status === "string") {
|
|
192
|
+
this.logger.conf.status = obj.status
|
|
193
|
+
changed = true
|
|
194
|
+
}
|
|
195
|
+
if (typeof obj.size === "string") {
|
|
196
|
+
this.logger.conf.size = obj.size
|
|
197
|
+
changed = true
|
|
198
|
+
}
|
|
199
|
+
if (changed) {
|
|
200
|
+
res.status(200).json(true).end()
|
|
201
|
+
} else {
|
|
202
|
+
res.status(204).json(false).end()
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
export function isValidSkips(s: string[]): boolean {
|
|
207
|
+
for (const x of s) {
|
|
208
|
+
if (!(typeof x === "string")) {
|
|
209
|
+
return false
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return true
|
|
213
|
+
}
|
|
214
|
+
export function mask(s: string, start: number, end: number, replace: string): string {
|
|
215
|
+
if (start < 0) {
|
|
216
|
+
start = 0
|
|
217
|
+
}
|
|
218
|
+
if (end < 0) {
|
|
219
|
+
end = 0
|
|
220
|
+
}
|
|
221
|
+
const t = start + end
|
|
222
|
+
if (t >= s.length) {
|
|
223
|
+
return replace.repeat(s.length)
|
|
224
|
+
}
|
|
225
|
+
return s.substr(0, start) + replace.repeat(s.length - t) + s.substr(s.length - end)
|
|
226
|
+
}
|
|
227
|
+
export function margin(s: string, start: number, end: number, replace: string): string {
|
|
228
|
+
if (start >= end) {
|
|
229
|
+
return ""
|
|
230
|
+
}
|
|
231
|
+
if (start < 0) {
|
|
232
|
+
start = 0
|
|
233
|
+
}
|
|
234
|
+
if (end < 0) {
|
|
235
|
+
end = 0
|
|
236
|
+
}
|
|
237
|
+
if (start >= s.length) {
|
|
238
|
+
return replace.repeat(s.length)
|
|
239
|
+
}
|
|
240
|
+
if (end >= s.length) {
|
|
241
|
+
return replace.repeat(start) + s.substr(start)
|
|
242
|
+
}
|
|
243
|
+
return replace.repeat(start) + s.substr(start, end - start) + replace.repeat(s.length - end)
|
|
244
|
+
}
|
|
245
|
+
export const maskMargin = margin
|
package/src/metadata.ts
CHANGED
|
@@ -1,34 +1,45 @@
|
|
|
1
|
-
export type
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
export type Type =
|
|
2
|
+
| "ObjectId"
|
|
3
|
+
| "date"
|
|
4
|
+
| "datetime"
|
|
5
|
+
| "time"
|
|
6
|
+
| "boolean"
|
|
7
|
+
| "number"
|
|
8
|
+
| "integer"
|
|
9
|
+
| "string"
|
|
10
|
+
| "text"
|
|
11
|
+
| "object"
|
|
12
|
+
| "array"
|
|
13
|
+
| "binary"
|
|
14
|
+
| "primitives"
|
|
15
|
+
| "booleans"
|
|
16
|
+
| "numbers"
|
|
17
|
+
| "integers"
|
|
18
|
+
| "strings"
|
|
19
|
+
| "dates"
|
|
20
|
+
| "datetimes"
|
|
21
|
+
| "times"
|
|
22
|
+
export type Format = "currency" | "percentage" | "email" | "url" | "phone" | "fax" | "ipv4" | "ipv6"
|
|
5
23
|
|
|
24
|
+
export type DataType = Type
|
|
25
|
+
export type FormatType = Format
|
|
6
26
|
export interface ErrorMessage {
|
|
7
|
-
field: string
|
|
8
|
-
code: string
|
|
9
|
-
|
|
10
|
-
|
|
27
|
+
field: string
|
|
28
|
+
code: string
|
|
29
|
+
message?: string
|
|
30
|
+
param?: string | number | Date
|
|
31
|
+
invalid?: string
|
|
11
32
|
}
|
|
12
33
|
|
|
13
34
|
export interface Model {
|
|
14
|
-
attributes: Attributes
|
|
35
|
+
attributes: Attributes
|
|
15
36
|
}
|
|
16
37
|
|
|
17
38
|
export interface Attribute {
|
|
18
|
-
name?: string
|
|
19
|
-
type?:
|
|
20
|
-
|
|
21
|
-
required?: boolean;
|
|
22
|
-
key?: boolean;
|
|
23
|
-
length?: number;
|
|
24
|
-
min?: number;
|
|
25
|
-
max?: number;
|
|
26
|
-
gt?: number;
|
|
27
|
-
lt?: number;
|
|
28
|
-
exp?: RegExp|string;
|
|
29
|
-
code?: string;
|
|
30
|
-
typeof?: Model;
|
|
39
|
+
name?: string
|
|
40
|
+
type?: Type
|
|
41
|
+
key?: boolean
|
|
31
42
|
}
|
|
32
43
|
export interface Attributes {
|
|
33
|
-
[key: string]: Attribute
|
|
44
|
+
[key: string]: Attribute
|
|
34
45
|
}
|
package/src/resources.ts
CHANGED
|
@@ -1,10 +1,149 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Application, NextFunction, Request, Response } from "express"
|
|
2
|
+
import * as fs from "fs"
|
|
3
|
+
import * as http from "http"
|
|
4
|
+
import * as https from "https"
|
|
5
|
+
import { Attributes, ErrorMessage } from "./metadata"
|
|
2
6
|
|
|
7
|
+
export interface StringMap {
|
|
8
|
+
[key: string]: string
|
|
9
|
+
}
|
|
3
10
|
// tslint:disable-next-line:class-name
|
|
4
11
|
export class resources {
|
|
5
|
-
static
|
|
12
|
+
static languageParam = "lang"
|
|
13
|
+
static defaultLanguage = "en"
|
|
14
|
+
static limits = [12, 24, 60, 100, 120, 180, 300, 600]
|
|
15
|
+
static pages = "pages"
|
|
16
|
+
static page = "page"
|
|
17
|
+
static nextPageToken = "next"
|
|
18
|
+
static limit = "limit"
|
|
19
|
+
static defaultLimit = 12
|
|
20
|
+
static sort = "sort"
|
|
21
|
+
static fields = "fields"
|
|
22
|
+
static partial = "partial"
|
|
23
|
+
static subPartial = "sub"
|
|
24
|
+
static log?: (msg: string) => void
|
|
25
|
+
static returnServerError?: boolean
|
|
26
|
+
static createValidator?: <T>(attributes: Attributes, allowUndefined?: boolean, max?: number) => Validator<T>
|
|
27
|
+
static check: (obj: any, attributes: Attributes, allowUndefined?: boolean, patch?: boolean) => ErrorMessage[]
|
|
28
|
+
static encoding?: BufferEncoding = "utf-8"
|
|
29
|
+
}
|
|
30
|
+
export function getView(req: Request, view: string): string {
|
|
31
|
+
const partial = req.query[resources.partial]
|
|
32
|
+
return partial === "true" ? resources.pages + "/" + view : view
|
|
33
|
+
}
|
|
34
|
+
export function isPartial(req: Request): boolean {
|
|
35
|
+
return req.query[resources.partial] === "true"
|
|
36
|
+
}
|
|
37
|
+
export function isSubPartial(req: Request): boolean {
|
|
38
|
+
return req.query[resources.subPartial] === "true"
|
|
6
39
|
}
|
|
7
|
-
|
|
8
40
|
export interface Validator<T> {
|
|
9
|
-
validate(obj: T, patch?: boolean): Promise<ErrorMessage[]
|
|
41
|
+
validate(obj: T, resource?: StringMap, patch?: boolean): Promise<ErrorMessage[]>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// tslint:disable-next-line:max-classes-per-file
|
|
45
|
+
export class TypeChecker {
|
|
46
|
+
constructor(
|
|
47
|
+
public attributes: Attributes,
|
|
48
|
+
public allowUndefined?: boolean,
|
|
49
|
+
) {
|
|
50
|
+
this.check = this.check.bind(this)
|
|
51
|
+
}
|
|
52
|
+
check(req: Request, res: Response, next: NextFunction): void {
|
|
53
|
+
const obj = req.body
|
|
54
|
+
if (!obj || (obj as any) === "") {
|
|
55
|
+
res.status(400).end("The request body cannot be empty")
|
|
56
|
+
} else {
|
|
57
|
+
const errors = resources.check(obj, this.attributes, this.allowUndefined)
|
|
58
|
+
if (errors.length > 0) {
|
|
59
|
+
res.status(400).json(errors).end()
|
|
60
|
+
} else {
|
|
61
|
+
next()
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export type Handler = (req: Request, res: Response, next: NextFunction) => void
|
|
67
|
+
export function check(attributes: Attributes, allowUndefined?: boolean): Handler {
|
|
68
|
+
const x = new TypeChecker(attributes, allowUndefined)
|
|
69
|
+
return x.check
|
|
70
|
+
}
|
|
71
|
+
export interface Parameter {
|
|
72
|
+
name: string
|
|
73
|
+
type: string
|
|
74
|
+
}
|
|
75
|
+
export interface StringFormat {
|
|
76
|
+
texts: string[]
|
|
77
|
+
parameters: Parameter[]
|
|
78
|
+
}
|
|
79
|
+
export interface Template {
|
|
80
|
+
name?: string | null
|
|
81
|
+
text: string
|
|
82
|
+
templates: TemplateNode[]
|
|
83
|
+
}
|
|
84
|
+
export interface TemplateNode {
|
|
85
|
+
type: string
|
|
86
|
+
text: string
|
|
87
|
+
property: string | null
|
|
88
|
+
encode?: string | null
|
|
89
|
+
value: string | null
|
|
90
|
+
format: StringFormat
|
|
91
|
+
array?: string | null
|
|
92
|
+
separator?: string | null
|
|
93
|
+
suffix?: string | null
|
|
94
|
+
prefix?: string | null
|
|
95
|
+
}
|
|
96
|
+
export function loadTemplates(
|
|
97
|
+
ok: boolean | undefined,
|
|
98
|
+
buildTemplates: (streams: string[], correct?: (stream: string) => string) => Map<string, Template>,
|
|
99
|
+
correct?: (stream: string) => string,
|
|
100
|
+
files?: string[],
|
|
101
|
+
): Map<string, Template> | undefined {
|
|
102
|
+
if (!ok) {
|
|
103
|
+
return undefined
|
|
104
|
+
}
|
|
105
|
+
if (!files) {
|
|
106
|
+
files = ["./src/query.xml"]
|
|
107
|
+
}
|
|
108
|
+
const mappers: string[] = []
|
|
109
|
+
for (const file of files) {
|
|
110
|
+
const mapper = fs.readFileSync(file, "utf8")
|
|
111
|
+
mappers.push(mapper)
|
|
112
|
+
}
|
|
113
|
+
return buildTemplates(mappers, correct)
|
|
114
|
+
}
|
|
115
|
+
export interface Server {
|
|
116
|
+
port: number
|
|
117
|
+
https?: boolean
|
|
118
|
+
options?: any
|
|
119
|
+
key?: string
|
|
120
|
+
cert?: string
|
|
121
|
+
}
|
|
122
|
+
export function start(a: Application, s: Server): void {
|
|
123
|
+
process.on("uncaughtException", (err) => {
|
|
124
|
+
console.log(err)
|
|
125
|
+
})
|
|
126
|
+
if (s.https) {
|
|
127
|
+
if (s.options) {
|
|
128
|
+
https.createServer(s.options, a).listen(s.port, () => {
|
|
129
|
+
console.log("Use https and start server at port " + s.port)
|
|
130
|
+
})
|
|
131
|
+
} else if (s.key && s.cert && s.key.length > 0 && s.cert.length > 0) {
|
|
132
|
+
const options = {
|
|
133
|
+
key: fs.readFileSync(s.key),
|
|
134
|
+
cert: fs.readFileSync(s.cert),
|
|
135
|
+
}
|
|
136
|
+
https.createServer(options, a).listen(s.port, () => {
|
|
137
|
+
console.log("Use https and start server at port " + s.port)
|
|
138
|
+
})
|
|
139
|
+
} else {
|
|
140
|
+
http.createServer(a).listen(s.port, () => {
|
|
141
|
+
console.log("Start server at port " + s.port)
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
http.createServer(a).listen(s.port, () => {
|
|
146
|
+
console.log("Start server at port " + s.port)
|
|
147
|
+
})
|
|
148
|
+
}
|
|
10
149
|
}
|