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/index.ts CHANGED
@@ -1,13 +1,464 @@
1
- export * from './health';
2
- export * from './HealthController';
3
- export * from './metadata';
4
- export * from './view';
5
- export * from './response';
6
- export * from './LoadController';
7
- export * from './search';
8
- export * from './SearchController';
9
- export * from './LoadSearchController';
10
- export * from './resources';
11
- export * from './edit';
12
- export * from './GenericController';
13
- export * from './GenericSearchController';
1
+ import { Request, Response } from "express"
2
+ import { GenericController } from "./GenericController"
3
+ import { GenericSearchController } from "./GenericSearchController"
4
+ import { HealthController } from "./HealthController"
5
+ import { handleError, query } from "./http"
6
+ import { LoadController } from "./LoadController"
7
+ import { LoadSearchController } from "./LoadSearchController"
8
+ import { LogController } from "./LogController"
9
+ import { Controller, Service } from "./LowCodeController"
10
+ import { ErrorMessage } from "./metadata"
11
+ import { resources, StringMap } from "./resources"
12
+ import { SearchController } from "./SearchController"
13
+
14
+ export { HealthController as HealthHandler, LoadController as LoadHandler, LogController as LogHandler, LoadController as ViewHandler }
15
+ // export {LoadController as ViewController};
16
+
17
+ export {
18
+ GenericController as GenericHandler,
19
+ GenericSearchController as GenericSearchHandler,
20
+ Controller as Handler,
21
+ LoadSearchController as LoadSearchHandler,
22
+ Service as LowCodeService,
23
+ SearchController as SearchHandler,
24
+ }
25
+
26
+ export * from "./access"
27
+ export * from "./client"
28
+ export * from "./edit"
29
+ export * from "./GenericController"
30
+ export * from "./GenericSearchController"
31
+ export * from "./health"
32
+ export * from "./HealthController"
33
+ export * from "./http"
34
+ export * from "./LoadController"
35
+ export * from "./LoadSearchController"
36
+ export * from "./log"
37
+ export * from "./LogController"
38
+ export * from "./LowCodeController"
39
+ export * from "./metadata"
40
+ export * from "./resources"
41
+ export * from "./search"
42
+ export * from "./SearchController"
43
+ export * from "./view"
44
+
45
+ export interface SavedService {
46
+ save(userId: string, id: string): Promise<number>
47
+ remove(userId: string, id: string): Promise<number>
48
+ }
49
+ export class SavedController {
50
+ constructor(
51
+ protected savedService: SavedService,
52
+ id?: string,
53
+ userId?: string,
54
+ ) {
55
+ this.userId = userId && userId.length > 0 ? userId : "userId"
56
+ this.id = id && id.length > 0 ? id : "id"
57
+ this.save = this.save.bind(this)
58
+ this.remove = this.remove.bind(this)
59
+ }
60
+ protected userId: string
61
+ protected id: string
62
+ save(req: Request, res: Response) {
63
+ const userId: string = res.locals[this.userId]
64
+ const id = req.params[this.id] as string
65
+ if (!id || id.length === 0) {
66
+ res.status(400).end(`'${this.id}' cannot be empty`)
67
+ return
68
+ }
69
+ if (!userId || userId.length === 0) {
70
+ res.status(400).end(`'${this.userId}' cannot be empty`)
71
+ return
72
+ }
73
+ this.savedService
74
+ .save(userId, id)
75
+ .then((result) => {
76
+ const status = result > 0 ? 200 : result === 0 ? 409 : 422
77
+ res.status(status).json(result).end()
78
+ })
79
+ .catch((err) => handleError(err, res))
80
+ }
81
+ remove(req: Request, res: Response) {
82
+ const userId: string = res.locals[this.userId]
83
+ const id = req.params[this.id] as string
84
+ if (!id || id.length === 0) {
85
+ res.status(400).end(`'${this.id}' cannot be empty`)
86
+ return
87
+ }
88
+ if (!userId || userId.length === 0) {
89
+ res.status(400).end(`'${this.userId}' cannot be empty`)
90
+ return
91
+ }
92
+ this.savedService
93
+ .remove(userId, id)
94
+ .then((result) => {
95
+ const status = result > 0 ? 200 : 410
96
+ res.status(status).json(result).end()
97
+ })
98
+ .catch((err) => handleError(err, res))
99
+ }
100
+ }
101
+ export interface FollowService {
102
+ follow(id: string, target: string): Promise<number>
103
+ unfollow(id: string, target: string): Promise<number>
104
+ }
105
+ // tslint:disable-next-line:max-classes-per-file
106
+ export class FollowController {
107
+ constructor(
108
+ protected service: FollowService,
109
+ id?: string,
110
+ userId?: string,
111
+ ) {
112
+ this.userId = userId && userId.length > 0 ? userId : "userId"
113
+ this.id = id && id.length > 0 ? id : "id"
114
+ this.follow = this.follow.bind(this)
115
+ this.unfollow = this.unfollow.bind(this)
116
+ }
117
+ protected userId: string
118
+ protected id: string
119
+ follow(req: Request, res: Response) {
120
+ const userId: string = res.locals[this.userId]
121
+ const id = req.params[this.id] as string
122
+ if (!id || id.length === 0) {
123
+ res.status(400).end(`'${this.id}' cannot be empty`)
124
+ return
125
+ }
126
+ if (!userId || userId.length === 0) {
127
+ res.status(400).end(`'${this.userId}' cannot be empty`)
128
+ return
129
+ }
130
+ this.service
131
+ .follow(userId, id)
132
+ .then((result) => {
133
+ const status = result > 0 ? 200 : 409
134
+ res.status(status).json(result).end()
135
+ })
136
+ .catch((err) => handleError(err, res))
137
+ }
138
+ unfollow(req: Request, res: Response) {
139
+ const userId: string = res.locals[this.userId]
140
+ const id = req.params[this.id] as string
141
+ if (!id || id.length === 0) {
142
+ res.status(400).end(`'${this.id}' cannot be empty`)
143
+ return
144
+ }
145
+ if (!userId || userId.length === 0) {
146
+ res.status(400).end(`'${this.userId}' cannot be empty`)
147
+ return
148
+ }
149
+ this.service
150
+ .unfollow(userId, id)
151
+ .then((result) => {
152
+ const status = result > 0 ? 200 : 410
153
+ res.status(status).json(result).end()
154
+ })
155
+ .catch((err) => handleError(err, res))
156
+ }
157
+ }
158
+ export interface ReactService {
159
+ react(id: string, author: string, reaction: string): Promise<number>
160
+ unreact(id: string, author: string, reaction: string): Promise<number>
161
+ checkReaction(id: string, author: string): Promise<number>
162
+ }
163
+ // tslint:disable-next-line:max-classes-per-file
164
+ export class UserReactionController {
165
+ constructor(
166
+ protected service: ReactService,
167
+ protected author: string,
168
+ id: string,
169
+ protected reaction: string,
170
+ ) {
171
+ this.id = id && id.length > 0 ? id : "id"
172
+ this.react = this.react.bind(this)
173
+ this.unreact = this.unreact.bind(this)
174
+ this.checkReaction = this.checkReaction.bind(this)
175
+ }
176
+ id: string
177
+ react(req: Request, res: Response) {
178
+ const id = req.params.id as string
179
+ const author = req.params.author as string
180
+ const reaction = req.params.reaction as string
181
+ if (!id || id.length === 0) {
182
+ res.status(400).end(`'${this.id}' cannot be empty`)
183
+ return
184
+ }
185
+ if (!author || author.length === 0) {
186
+ res.status(400).end(`'${this.author}' cannot be empty`)
187
+ return
188
+ }
189
+ if (!reaction || reaction.length === 0) {
190
+ res.status(400).end(`'${this.reaction}' cannot be empty`)
191
+ return
192
+ }
193
+ this.service
194
+ .react(id, author, reaction)
195
+ .then((count) => {
196
+ res.status(200).json(count).end()
197
+ })
198
+ .catch((err) => handleError(err, res))
199
+ }
200
+ unreact(req: Request, res: Response) {
201
+ const id = req.params.id as string
202
+ const author = req.params.author as string
203
+ const reaction = req.params.reaction as string
204
+ if (!id || id.length === 0) {
205
+ res.status(400).end(`'${this.id}' cannot be empty`)
206
+ return
207
+ }
208
+ if (!author || author.length === 0) {
209
+ res.status(400).end(`'${this.author}' cannot be empty`)
210
+ return
211
+ }
212
+ if (!reaction || reaction.length === 0) {
213
+ res.status(400).end(`'${this.reaction}' cannot be empty`)
214
+ return
215
+ }
216
+ this.service
217
+ .unreact(id, author, reaction)
218
+ .then((count) => {
219
+ res.status(200).json(count).end()
220
+ })
221
+ .catch((err) => handleError(err, res))
222
+ }
223
+ checkReaction(req: Request, res: Response) {
224
+ const id = req.params.id as string
225
+ const author = req.params.author as string
226
+ if (!id || id.length === 0) {
227
+ res.status(400).end(`'${this.id}' cannot be empty`)
228
+ return
229
+ }
230
+ if (!author || author.length === 0) {
231
+ res.status(400).end(`'${this.author}' cannot be empty`)
232
+ return
233
+ }
234
+ this.service
235
+ .checkReaction(id, author)
236
+ .then((count) => {
237
+ res.status(200).json(count).end()
238
+ })
239
+ .catch((err) => handleError(err, res))
240
+ }
241
+ }
242
+ export const ReactController = UserReactionController
243
+ export const ReactionController = UserReactionController
244
+
245
+ export function checked(s: string[] | string | undefined, v: string): boolean | undefined {
246
+ if (s) {
247
+ if (Array.isArray(s)) {
248
+ return s.includes(v)
249
+ } else {
250
+ return s === v
251
+ }
252
+ }
253
+ return false
254
+ }
255
+ export function addSeconds(date: Date, number: number): Date {
256
+ const d = new Date(date)
257
+ d.setSeconds(d.getSeconds() + number)
258
+ return d
259
+ }
260
+ export function addMinutes(date: Date, number: number): Date {
261
+ const d = new Date(date)
262
+ d.setMinutes(d.getMinutes() + number)
263
+ return d
264
+ }
265
+ export function addDays(d: Date, n: number): Date {
266
+ const newDate = new Date(d)
267
+ newDate.setDate(newDate.getDate() + n)
268
+ return newDate
269
+ }
270
+ export interface ErrorMap {
271
+ [key: string]: ErrorMessage
272
+ }
273
+ export function toMap(errors: ErrorMessage[]): ErrorMap {
274
+ const errorMap: ErrorMap = {}
275
+ if (!errors) {
276
+ return errorMap
277
+ }
278
+ for (let i = 0; i < errors.length; i++) {
279
+ errors[i].invalid = "invalid"
280
+ errorMap[errors[i].field] = errors[i]
281
+ }
282
+ return errorMap
283
+ }
284
+
285
+ interface SaveService<T> {
286
+ create(obj: T, ctx?: any): Promise<number | T | ErrorMessage[]>
287
+ update(obj: T, ctx?: any): Promise<number | T | ErrorMessage[]>
288
+ }
289
+ export function isSuccessful<T>(res: number | T | ErrorMessage[]): boolean {
290
+ return (typeof res === "number" && res <= 0) || Array.isArray(res) ? false : true
291
+ }
292
+ export function afterCreated<T>(res: Response, result: number | T | ErrorMessage[], returnObject?: boolean): void {
293
+ if (Array.isArray(result)) {
294
+ res.status(422).json(result).end()
295
+ } else if (typeof result === "number") {
296
+ if (result > 0) {
297
+ res.status(200).json(result).end()
298
+ } else {
299
+ res.status(409).json(result).end()
300
+ }
301
+ } else {
302
+ res
303
+ .status(201)
304
+ .json(returnObject ? result : 1)
305
+ .end()
306
+ }
307
+ }
308
+ export function respond<T>(res: Response, result: number | T | ErrorMessage[], returnObject?: boolean): void {
309
+ if (Array.isArray(result)) {
310
+ res.status(422).json(result).end()
311
+ } else if (typeof result === "number") {
312
+ if (result > 0) {
313
+ res.status(200).json(result).end()
314
+ } else if (result === 0) {
315
+ res.status(410).json(result).end()
316
+ } else {
317
+ res.status(409).json(result).end()
318
+ }
319
+ } else {
320
+ res
321
+ .status(200)
322
+ .json(returnObject ? result : 1)
323
+ .end()
324
+ }
325
+ }
326
+ export function save<T>(isEdit: boolean, res: Response, obj: T, service: SaveService<T>, returnNumber?: boolean) {
327
+ if (!isEdit) {
328
+ service
329
+ .create(obj)
330
+ .then((result) => {
331
+ if (Array.isArray(result)) {
332
+ res.status(422).json(result).end()
333
+ } else if (typeof result === "number" && result <= 0) {
334
+ res.status(409).json(result).end()
335
+ } else {
336
+ res.status(201).json(obj).end()
337
+ }
338
+ })
339
+ .catch((err) => handleError(err, res))
340
+ } else {
341
+ service
342
+ .update(obj)
343
+ .then((result) => {
344
+ if (result === 0) {
345
+ res.status(410).end()
346
+ } else if (Array.isArray(result)) {
347
+ res.status(422).json(result).end()
348
+ } else if (typeof result === "number" && result < 0) {
349
+ res.status(409).json(result).end()
350
+ } else {
351
+ res
352
+ .status(200)
353
+ .json(returnNumber ? result : obj)
354
+ .end()
355
+ }
356
+ })
357
+ .catch((err) => handleError(err, res))
358
+ }
359
+ }
360
+ const map: StringMap = {
361
+ "&": "&amp;",
362
+ "<": "&lt;",
363
+ ">": "&gt;",
364
+ '"': "&quot;",
365
+ "'": "&#39;",
366
+ "`": "&#96;",
367
+ }
368
+ export function escapeHTML(input: string): string {
369
+ return input.replace(/[&<>"'`]/g, function (char) {
370
+ return map[char]
371
+ })
372
+ }
373
+ export function generateChip(value: string, text: string, noClose?: boolean, hasStar?: boolean): string {
374
+ const s = noClose ? "" : `<span class="close" onclick="removeChip(event)"></span>`
375
+ const t = hasStar ? `<i class="star highlight"></i>` : ""
376
+ return `<div class="chip" data-value="${escapeHTML(value)}">${escapeHTML(text)}${t}${s}</div>`
377
+ }
378
+ export function generateTags(v?: string[] | null, noClose?: boolean): string {
379
+ return !v ? "" : `${v.map((s) => generateChip(s, s, noClose)).join("")}`
380
+ }
381
+ export interface Item {
382
+ value: string
383
+ text: string
384
+ }
385
+ export function generateChips(v?: Item[] | null, noClose?: boolean): string {
386
+ return !v ? "" : `${v.map((s) => generateChip(s.value, s.text, noClose)).join("")}`
387
+ }
388
+ export function generateStarChips(v: any[] | null | undefined, value: string, text: string, star: string, noClose?: boolean): string {
389
+ return !v ? "" : `${v.map((s) => generateChip(s[value], s[text], noClose, s[star] === true)).join("")}`
390
+ }
391
+
392
+ const s = "string"
393
+ const o = "object"
394
+ export function escape(obj: any): any {
395
+ if (!obj || typeof obj !== s) {
396
+ return obj
397
+ }
398
+ const keys = Object.keys(obj)
399
+ for (const key of keys) {
400
+ const v = obj[key]
401
+ if (typeof v === s) {
402
+ obj[key] = escapeHTML(v)
403
+ } else if (Array.isArray(v) && v.length > 0) {
404
+ const v1 = v[0]
405
+ if (typeof v1 === o && !(v1 instanceof Date)) {
406
+ for (const item of v) {
407
+ escape(item)
408
+ }
409
+ }
410
+ } else if (typeof v === o && !(v instanceof Date)) {
411
+ escape(obj[key])
412
+ }
413
+ }
414
+ return obj
415
+ }
416
+ export function escapeArray<T>(arrs: T[], offset: number = 0, name?: string): T[] {
417
+ if (!arrs) {
418
+ return arrs
419
+ }
420
+ if (arrs.length > 0) {
421
+ for (const obj of arrs) {
422
+ escape(obj)
423
+ }
424
+ }
425
+ if (name) {
426
+ const l = arrs.length
427
+ for (let i = 0; i < l; i++) {
428
+ ;(arrs[i] as any)[name] = offset + i + 1
429
+ }
430
+ }
431
+ return arrs
432
+ }
433
+
434
+ export function buildError404(resource: StringMap, res: Response): any {
435
+ return {
436
+ message: {
437
+ title: resource.error_404_title,
438
+ description: resource.error_404_message,
439
+ },
440
+ menu: res.locals.menu,
441
+ }
442
+ }
443
+ export function buildError500(resource: StringMap, res: Response): any {
444
+ return {
445
+ message: {
446
+ title: resource.error_500_title,
447
+ description: resource.error_500_message,
448
+ },
449
+ menu: res.locals.menu,
450
+ }
451
+ }
452
+ export function buildError(res: Response, title: string, description: string): any {
453
+ return {
454
+ message: {
455
+ title,
456
+ description,
457
+ },
458
+ menu: res.locals.menu,
459
+ }
460
+ }
461
+ export function queryLang(req: Request): string {
462
+ const lang = query(req, resources.languageParam)
463
+ return lang && lang.length > 0 ? lang : resources.defaultLanguage
464
+ }