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/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 DataType = 'ObjectId' | 'date' | 'datetime' | 'time'
2
- | 'boolean' | 'number' | 'integer' | 'string' | 'text'
3
- | 'object' | 'array' | 'primitives' | 'binary';
4
- export type FormatType = 'currency' | 'percentage' | 'email' | 'url' | 'phone' | 'fax' | 'ipv4' | 'ipv6';
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
- param?: string|number|Date;
10
- message?: string;
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?: DataType;
20
- format?: FormatType;
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 {Attributes, ErrorMessage} from './metadata';
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 createValidator?: <T>(attributes: Attributes, allowUndefined?: boolean, max?: number) => Validator<T>;
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
  }