5htp-core 0.2.4 → 0.2.5
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/package.json +5 -2
- package/src/client/assets/css/text/text.less +0 -26
- package/src/client/components/Amount.tsx +1 -1
- package/src/client/pages/_layout/index.less +6 -0
- package/src/client/pages/_layout/index.tsx +43 -0
- package/src/client/pages/_messages/400.tsx +8 -9
- package/src/client/pages/_messages/401.tsx +20 -3
- package/src/client/pages/_messages/403.tsx +6 -6
- package/src/client/pages/_messages/404.tsx +7 -7
- package/src/client/pages/_messages/500.tsx +6 -6
- package/src/client/services/router/index.tsx +5 -1
- package/src/common/errors/index.ts +1 -1
- package/src/common/router/layouts.ts +22 -23
- package/src/common/validation/index.ts +2 -1
- package/src/server/app/index.ts +19 -3
- package/src/server/app/service.ts +2 -0
- package/src/server/services/cache/index.ts +0 -0
- package/src/server/services/console/index.ts +86 -4
- package/src/server/services/database/connection.ts +46 -32
- package/src/server/services/router/index.ts +13 -5
- package/src/client/pages/_layout/base.less +0 -13
- package/src/client/pages/_layout/landing/index.less +0 -5
- package/src/client/pages/_layout/landing/index.tsx +0 -55
- package/src/client/pages/_messages/403.svg +0 -1835
- package/src/client/pages/_messages/404.svg +0 -205
- package/src/client/pages/_messages/500.svg +0 -396
- package/src/server/services/console/bugReporter.ts +0 -250
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "5htp-core",
|
|
3
3
|
"description": "Convenient TypeScript framework designed for Performance and Productivity.",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.5",
|
|
5
5
|
"author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
|
|
6
6
|
"repository": "git://github.com/gaetanlegac/5htp-core.git",
|
|
7
7
|
"license": "MIT",
|
|
@@ -92,5 +92,8 @@
|
|
|
92
92
|
"@types/ws": "^7.4.7",
|
|
93
93
|
"@types/yargs-parser": "^21.0.0",
|
|
94
94
|
"babel-plugin-glob-import": "^0.0.6-2"
|
|
95
|
-
}
|
|
95
|
+
},
|
|
96
|
+
"peerDependencies": {
|
|
97
|
+
"5htp": "0.2.3"
|
|
98
|
+
}
|
|
96
99
|
}
|
|
@@ -54,32 +54,6 @@ p, .p {
|
|
|
54
54
|
text-overflow: ellipsis;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
/*----------------------------------
|
|
58
|
-
- COULEURS
|
|
59
|
-
----------------------------------*/
|
|
60
|
-
.txtSuccess {
|
|
61
|
-
color: var(--cSuccess) !important;
|
|
62
|
-
}
|
|
63
|
-
.txtError {
|
|
64
|
-
color: var(--cError) !important;
|
|
65
|
-
}
|
|
66
|
-
.txt-gold {
|
|
67
|
-
/*color: #cef57a;
|
|
68
|
-
text-shadow: 0 0 0.80rem fade(#cef57a, 80%);*/
|
|
69
|
-
color: #c80;
|
|
70
|
-
}
|
|
71
|
-
.txtWarn {
|
|
72
|
-
color: var(--orange) !important;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
.txt-blanc { color: #fff !important; }
|
|
76
|
-
|
|
77
|
-
.txtPrimary, .txtC1 {
|
|
78
|
-
color: var(--cAccent);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
.txtC2 { color: var(--cAccent2); }
|
|
82
|
-
|
|
83
57
|
/*----------------------------------
|
|
84
58
|
- TAILLE
|
|
85
59
|
----------------------------------*/
|
|
@@ -24,7 +24,7 @@ export default ({ amount, unit, sign, decimals, ...props }: {
|
|
|
24
24
|
sign?: '+' | '-'
|
|
25
25
|
} & JSX.HTMLAttributes<HTMLDivElement>) => {
|
|
26
26
|
|
|
27
|
-
const className = 'number row sp-05 ' + (props.class ? props.class + ' ' : '');//sign === undefined ? 'txtPrimary' : (sign === '+' ? '
|
|
27
|
+
const className = 'number row sp-05 ' + (props.class ? props.class + ' ' : '');//sign === undefined ? 'txtPrimary' : (sign === '+' ? 'fg success' : 'fg error');
|
|
28
28
|
if (unit === 'credits')
|
|
29
29
|
return <strong {...props} class={className}>{sign} {Format.credits(amount, decimals)} Credits</strong>;
|
|
30
30
|
else if (unit === 'dollars')
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// Npm
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import type { ComponentChild } from 'preact';
|
|
8
|
+
|
|
9
|
+
// Core
|
|
10
|
+
import Router from '@client/services/router/components/router';
|
|
11
|
+
import { ClientContext } from '@/client/context';
|
|
12
|
+
|
|
13
|
+
// Core components
|
|
14
|
+
|
|
15
|
+
// Resources
|
|
16
|
+
import "./index.less";
|
|
17
|
+
|
|
18
|
+
/*----------------------------------
|
|
19
|
+
- TYPES
|
|
20
|
+
----------------------------------*/
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
/*----------------------------------
|
|
24
|
+
- COMPOSANT
|
|
25
|
+
----------------------------------*/
|
|
26
|
+
export default function App ({ context, menu }: {
|
|
27
|
+
context: ClientContext,
|
|
28
|
+
menu: ComponentChild
|
|
29
|
+
}) {
|
|
30
|
+
|
|
31
|
+
const { router, page, toast } = context;
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div id="internaLlayout">
|
|
35
|
+
|
|
36
|
+
<div class="center row al-fill">
|
|
37
|
+
|
|
38
|
+
<Router service={router} />
|
|
39
|
+
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
@@ -7,7 +7,7 @@ import React from 'react';
|
|
|
7
7
|
|
|
8
8
|
// Core
|
|
9
9
|
import { router } from '@app';
|
|
10
|
-
import Button from '@client/components
|
|
10
|
+
import { Button } from '@client/components';
|
|
11
11
|
|
|
12
12
|
// App
|
|
13
13
|
import useHeader from '@client/pages/useHeader';
|
|
@@ -15,10 +15,7 @@ import useHeader from '@client/pages/useHeader';
|
|
|
15
15
|
/*----------------------------------
|
|
16
16
|
- CONTROLEUR
|
|
17
17
|
----------------------------------*/
|
|
18
|
-
router.error(400,
|
|
19
|
-
|
|
20
|
-
if (!message)
|
|
21
|
-
message = "The request you made is incorrect.";
|
|
18
|
+
router.error( 400, ({ message, modal }) => {
|
|
22
19
|
|
|
23
20
|
useHeader({
|
|
24
21
|
title: 'Bad request',
|
|
@@ -26,13 +23,15 @@ router.error(400, {}, ({ message, modal }) => {
|
|
|
26
23
|
});
|
|
27
24
|
|
|
28
25
|
return (
|
|
29
|
-
<div class="col pd-2">
|
|
30
|
-
|
|
26
|
+
<div class="card w-3-4 col al-center pd-2">
|
|
27
|
+
|
|
28
|
+
<i src="times-circle" class="fg error xxl" />
|
|
31
29
|
|
|
32
|
-
<h1>Bad
|
|
30
|
+
<h1>Bad Request</h1>
|
|
33
31
|
|
|
34
32
|
<p>{message}</p>
|
|
33
|
+
|
|
34
|
+
<Button type="primary" link="/">Go Home</Button>
|
|
35
35
|
</div>
|
|
36
36
|
)
|
|
37
|
-
|
|
38
37
|
});
|
|
@@ -7,9 +7,10 @@ import React from 'react';
|
|
|
7
7
|
|
|
8
8
|
// Core
|
|
9
9
|
import { router } from '@app';
|
|
10
|
-
import Button from '@client/components
|
|
10
|
+
import { Button } from '@client/components';
|
|
11
11
|
|
|
12
12
|
// App
|
|
13
|
+
import useHeader from '@client/pages/useHeader';
|
|
13
14
|
|
|
14
15
|
/*----------------------------------
|
|
15
16
|
- RESSOURCES
|
|
@@ -18,10 +19,15 @@ import Button from '@client/components/button';
|
|
|
18
19
|
/*----------------------------------
|
|
19
20
|
- CONTROLEUR
|
|
20
21
|
----------------------------------*/
|
|
21
|
-
router.error(401,
|
|
22
|
+
router.error( 401, ({ message, request, page }) => {
|
|
22
23
|
|
|
23
24
|
request.response?.redirect('/');
|
|
24
25
|
|
|
26
|
+
useHeader({
|
|
27
|
+
title: 'Authentication Required',
|
|
28
|
+
subtitle: message
|
|
29
|
+
});
|
|
30
|
+
|
|
25
31
|
React.useEffect(() => {
|
|
26
32
|
|
|
27
33
|
page?.go('/');
|
|
@@ -29,5 +35,16 @@ router.error(401, { }, ({ api, toast, modal, request, page }) => {
|
|
|
29
35
|
|
|
30
36
|
}, []);
|
|
31
37
|
|
|
32
|
-
return
|
|
38
|
+
return (
|
|
39
|
+
<div class="card w-3-4 col al-center pd-2">
|
|
40
|
+
|
|
41
|
+
<i src="times-circle" class="fg error xxl" />
|
|
42
|
+
|
|
43
|
+
<h1>Authentication Required</h1>
|
|
44
|
+
|
|
45
|
+
<p>{message}</p>
|
|
46
|
+
|
|
47
|
+
<Button type="primary" link="/">Go Home</Button>
|
|
48
|
+
</div>
|
|
49
|
+
)
|
|
33
50
|
});
|
|
@@ -7,7 +7,7 @@ import React from 'react';
|
|
|
7
7
|
|
|
8
8
|
// Core
|
|
9
9
|
import { router } from '@app';
|
|
10
|
-
import Button from '@client/components
|
|
10
|
+
import { Button } from '@client/components';
|
|
11
11
|
|
|
12
12
|
// App
|
|
13
13
|
import useHeader from '@client/pages/useHeader';
|
|
@@ -15,10 +15,7 @@ import useHeader from '@client/pages/useHeader';
|
|
|
15
15
|
/*----------------------------------
|
|
16
16
|
- CONTROLEUR
|
|
17
17
|
----------------------------------*/
|
|
18
|
-
router.error( 403,
|
|
19
|
-
|
|
20
|
-
if (!message)
|
|
21
|
-
message = "You do not have sufficient permissions to access this content.";
|
|
18
|
+
router.error( 403, ({ message, modal }) => {
|
|
22
19
|
|
|
23
20
|
useHeader({
|
|
24
21
|
title: 'Access Denied.',
|
|
@@ -27,11 +24,14 @@ router.error( 403, {}, ({ message, modal }) => {
|
|
|
27
24
|
|
|
28
25
|
return (
|
|
29
26
|
<div class="col pd-2">
|
|
30
|
-
|
|
27
|
+
|
|
28
|
+
<i src="times-circle" class="fg error xxl" />
|
|
31
29
|
|
|
32
30
|
<h1>Access Denied.</h1>
|
|
33
31
|
|
|
34
32
|
<p>{message}</p>
|
|
33
|
+
|
|
34
|
+
<Button type="primary" link="/">Go Home</Button>
|
|
35
35
|
</div>
|
|
36
36
|
)
|
|
37
37
|
});
|
|
@@ -7,7 +7,7 @@ import React from 'react';
|
|
|
7
7
|
|
|
8
8
|
// Core
|
|
9
9
|
import { router } from '@app';
|
|
10
|
-
import Button from '@client/components
|
|
10
|
+
import { Button } from '@client/components';
|
|
11
11
|
|
|
12
12
|
// App
|
|
13
13
|
import useHeader from '@client/pages/useHeader';
|
|
@@ -15,10 +15,7 @@ import useHeader from '@client/pages/useHeader';
|
|
|
15
15
|
/*----------------------------------
|
|
16
16
|
- CONTROLEUR
|
|
17
17
|
----------------------------------*/
|
|
18
|
-
router.error( 404,
|
|
19
|
-
|
|
20
|
-
if (!message)
|
|
21
|
-
message = "The content you asked for was not found.";
|
|
18
|
+
router.error( 404, ({ message, modal }) => {
|
|
22
19
|
|
|
23
20
|
useHeader({
|
|
24
21
|
title: 'Page Not Found',
|
|
@@ -26,12 +23,15 @@ router.error( 404, {}, ({ message, modal }) => {
|
|
|
26
23
|
});
|
|
27
24
|
|
|
28
25
|
return (
|
|
29
|
-
<div class="col pd-2">
|
|
30
|
-
|
|
26
|
+
<div class="card w-3-4 col al-center pd-2">
|
|
27
|
+
|
|
28
|
+
<i src="times-circle" class="fg error xxl" />
|
|
31
29
|
|
|
32
30
|
<h1>Page Not Found</h1>
|
|
33
31
|
|
|
34
32
|
<p>{message}</p>
|
|
33
|
+
|
|
34
|
+
<Button type="primary" link="/">Go Home</Button>
|
|
35
35
|
</div>
|
|
36
36
|
)
|
|
37
37
|
});
|
|
@@ -7,7 +7,7 @@ import React from 'react';
|
|
|
7
7
|
|
|
8
8
|
// Core
|
|
9
9
|
import { router } from '@app';
|
|
10
|
-
import Button from '@client/components
|
|
10
|
+
import { Button } from '@client/components';
|
|
11
11
|
|
|
12
12
|
// App
|
|
13
13
|
import useHeader from '@client/pages/useHeader';
|
|
@@ -15,10 +15,7 @@ import useHeader from '@client/pages/useHeader';
|
|
|
15
15
|
/*----------------------------------
|
|
16
16
|
- CONTROLEUR
|
|
17
17
|
----------------------------------*/
|
|
18
|
-
router.error( 500,
|
|
19
|
-
|
|
20
|
-
if (!message)
|
|
21
|
-
message = "A technical error occurred.";
|
|
18
|
+
router.error( 500, ({ message }) => {
|
|
22
19
|
|
|
23
20
|
useHeader({
|
|
24
21
|
title: 'Technical Error',
|
|
@@ -27,11 +24,14 @@ router.error( 500, {}, ({ message }) => {
|
|
|
27
24
|
|
|
28
25
|
return (
|
|
29
26
|
<div class="col pd-2">
|
|
30
|
-
|
|
27
|
+
|
|
28
|
+
<i src="times-circle" class="fg error xxl" />
|
|
31
29
|
|
|
32
30
|
<h1>Technical Error</h1>
|
|
33
31
|
|
|
34
32
|
<p>{message}</p>
|
|
33
|
+
|
|
34
|
+
<Button type="primary" link="/">Go Home</Button>
|
|
35
35
|
</div>
|
|
36
36
|
)
|
|
37
37
|
});
|
|
@@ -17,6 +17,7 @@ import type { TBasicSSrData } from '@server/services/router/response';
|
|
|
17
17
|
import BaseRouter, {
|
|
18
18
|
defaultOptions, TRoute, TErrorRoute, TClientOrServerContext, TRouteModule
|
|
19
19
|
} from '@common/router'
|
|
20
|
+
import { getLayout } from '@common/router/layouts';
|
|
20
21
|
import { getRegisterPageArgs, buildRegex } from '@common/router/register';
|
|
21
22
|
import { TFetcherList } from '@common/router/request/api';
|
|
22
23
|
import type { TFrontRenderer, TDataProvider } from '@common/router/response/page';
|
|
@@ -243,9 +244,12 @@ export default class ClientRouter<
|
|
|
243
244
|
|
|
244
245
|
public error(code: number, options: TRoute["options"], renderer: TFrontRenderer<{}, { message: string }>) {
|
|
245
246
|
|
|
247
|
+
// Automatic layout form the nearest _layout folder
|
|
248
|
+
const layout = getLayout('Error ' + code, options);
|
|
249
|
+
|
|
246
250
|
const route: TErrorRoute = {
|
|
247
251
|
code,
|
|
248
|
-
controller: (context: TClientOrServerContext) => new ClientPage(null, renderer, context),
|
|
252
|
+
controller: (context: TClientOrServerContext) => new ClientPage(null, renderer, context, layout),
|
|
249
253
|
options
|
|
250
254
|
};
|
|
251
255
|
|
|
@@ -112,7 +112,7 @@ export class AuthRequired extends CoreError {
|
|
|
112
112
|
export class Forbidden extends CoreError {
|
|
113
113
|
public http = 403;
|
|
114
114
|
public title = "Access Denied";
|
|
115
|
-
public static msgDefaut = "You
|
|
115
|
+
public static msgDefaut = "You do not have sufficient permissions to access this content.";
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
export class NotFound extends CoreError {
|
|
@@ -8,6 +8,7 @@ import type { ComponentChild } from 'preact';
|
|
|
8
8
|
import type { ClientContext } from '@/client/context';
|
|
9
9
|
import type { TRouteOptions } from '.';
|
|
10
10
|
// App
|
|
11
|
+
import internalLayout from '@client/pages/_layout';
|
|
11
12
|
import layouts from '@/client/pages/**/_layout/index.tsx';
|
|
12
13
|
|
|
13
14
|
/*----------------------------------
|
|
@@ -43,8 +44,6 @@ export const getLayout = (routePath: string, routeOptions?: TRouteOptions): Layo
|
|
|
43
44
|
const chunkId = routeOptions["id"];
|
|
44
45
|
if (chunkId === undefined)
|
|
45
46
|
throw new Error(`ID has not injected for the following page route: ${routePath}`);
|
|
46
|
-
|
|
47
|
-
let layout: Layout | undefined;
|
|
48
47
|
|
|
49
48
|
// Layout via name
|
|
50
49
|
if (routeOptions.layout !== undefined) {
|
|
@@ -53,30 +52,30 @@ export const getLayout = (routePath: string, routeOptions?: TRouteOptions): Layo
|
|
|
53
52
|
if (LayoutComponent === undefined)
|
|
54
53
|
throw new Error(`No layout found with ID: ${routeOptions.layout}. registered layouts: ${Object.keys(layouts)}`);
|
|
55
54
|
|
|
56
|
-
|
|
55
|
+
return {
|
|
57
56
|
path: routeOptions.layout,
|
|
58
57
|
Component: layouts[routeOptions.layout]
|
|
59
58
|
}
|
|
60
|
-
|
|
61
|
-
} else {
|
|
59
|
+
}
|
|
62
60
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
//console.log(`[router][layouts] Get layout for "${routePath}". Found:`, layout);
|
|
61
|
+
// Automatic layout via the nearest _layout folder
|
|
62
|
+
for (const layoutPath in layouts)
|
|
63
|
+
if (
|
|
64
|
+
// The layout is nammed index when it's at the root (@/client/pages/_layout)
|
|
65
|
+
layoutPath === 'index'
|
|
66
|
+
// Exact match
|
|
67
|
+
|| chunkId === layoutPath
|
|
68
|
+
// Parent
|
|
69
|
+
|| chunkId.startsWith( layoutPath + '_' )
|
|
70
|
+
)
|
|
71
|
+
return {
|
|
72
|
+
path: layoutPath,
|
|
73
|
+
Component: layouts[layoutPath]
|
|
74
|
+
};
|
|
80
75
|
|
|
81
|
-
|
|
76
|
+
// Internal layout
|
|
77
|
+
return {
|
|
78
|
+
path: '/',
|
|
79
|
+
Component: internalLayout
|
|
80
|
+
}
|
|
82
81
|
}
|
package/src/server/app/index.ts
CHANGED
|
@@ -65,11 +65,13 @@ export default abstract class Application extends Service<Config, Hooks, /* TODO
|
|
|
65
65
|
|
|
66
66
|
public path = {
|
|
67
67
|
root: process.cwd(),
|
|
68
|
+
public: process.cwd() + '/public',
|
|
69
|
+
|
|
70
|
+
// TODO: move to disk
|
|
68
71
|
typings: process.cwd() + '/var/typings',
|
|
69
72
|
cache: process.cwd() + '/var/cache',
|
|
70
73
|
data: process.cwd() + '/var/data',
|
|
71
74
|
log: process.cwd() + '/var/log',
|
|
72
|
-
public: process.cwd() + '/public',
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
public pkg = fs.readJSONSync(this.path.root + '/package.json');
|
|
@@ -136,10 +138,15 @@ export default abstract class Application extends Service<Config, Hooks, /* TODO
|
|
|
136
138
|
|
|
137
139
|
public async start() {
|
|
138
140
|
|
|
139
|
-
console.
|
|
141
|
+
console.log(`5HTP Core`, process.env.npm_package_version);
|
|
142
|
+
|
|
143
|
+
console.info(`[boot] Connect disk`);
|
|
144
|
+
await this.initDisk();
|
|
145
|
+
|
|
146
|
+
console.info(`[boot] Start services`);
|
|
140
147
|
await this.startServices()
|
|
141
148
|
|
|
142
|
-
console.info(`[boot]
|
|
149
|
+
console.info(`[boot] App ready`);
|
|
143
150
|
await this.runHook('ready');
|
|
144
151
|
|
|
145
152
|
console.info(`[boot] Run application-specific boot instructions ...`);
|
|
@@ -150,6 +157,15 @@ export default abstract class Application extends Service<Config, Hooks, /* TODO
|
|
|
150
157
|
|
|
151
158
|
}
|
|
152
159
|
|
|
160
|
+
private async initDisk() {
|
|
161
|
+
console.info(`[boot] Ensure runtime dirs ...`);
|
|
162
|
+
await Promise.all([
|
|
163
|
+
fs.ensureDir( this.path.cache ),
|
|
164
|
+
fs.ensureDir( this.path.log ),
|
|
165
|
+
fs.ensureDir( this.path.data ),
|
|
166
|
+
]);
|
|
167
|
+
}
|
|
168
|
+
|
|
153
169
|
public registerService( service: AnyService ) {
|
|
154
170
|
console.log(`[app] Register service`, service.constructor?.name);
|
|
155
171
|
this.servicesList.push(service);
|
|
File without changes
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/*----------------------------------
|
|
2
2
|
- DEPENDANCES
|
|
3
3
|
----------------------------------*/
|
|
4
|
-
|
|
5
4
|
// Npm
|
|
5
|
+
import { v4 as uuid } from 'uuid';
|
|
6
6
|
import { Logger, ILogObject } from "tslog";
|
|
7
7
|
import { format as formatSql } from 'sql-formatter';
|
|
8
8
|
import highlight from 'cli-highlight';
|
|
@@ -11,10 +11,10 @@ import highlight from 'cli-highlight';
|
|
|
11
11
|
import Application, { Service, TPriority } from '@server/app';
|
|
12
12
|
import context from '@server/context';
|
|
13
13
|
import type ServerRequest from '@server/services/router/request';
|
|
14
|
+
import { SqlError } from '@server/services/database/debug';
|
|
14
15
|
|
|
15
16
|
// Specific
|
|
16
17
|
import logToHTML from './html';
|
|
17
|
-
import BugReporter from "./bugReporter";
|
|
18
18
|
|
|
19
19
|
/*----------------------------------
|
|
20
20
|
- SERVICE CONFIG
|
|
@@ -81,10 +81,36 @@ export type TLog = ChannelInfos & {
|
|
|
81
81
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
/*----------------------------------
|
|
85
|
+
- TYPES: BUG REPORT
|
|
86
|
+
----------------------------------*/
|
|
87
|
+
export type ServerBug = {
|
|
88
|
+
|
|
89
|
+
type: 'server',
|
|
90
|
+
|
|
91
|
+
// Context
|
|
92
|
+
hash: string,
|
|
93
|
+
date: Date, // Timestamp
|
|
94
|
+
channelType?: string,
|
|
95
|
+
channelId?: string,
|
|
96
|
+
|
|
97
|
+
user: string | null | undefined,
|
|
98
|
+
ip: string | null | undefined,
|
|
99
|
+
|
|
100
|
+
// Error
|
|
101
|
+
error: Error,
|
|
102
|
+
stacktrace: string,
|
|
103
|
+
logs: string,
|
|
104
|
+
}
|
|
105
|
+
|
|
84
106
|
/*----------------------------------
|
|
85
107
|
- CONST
|
|
86
108
|
----------------------------------*/
|
|
87
109
|
|
|
110
|
+
const LogPrefix = '[console]'
|
|
111
|
+
|
|
112
|
+
const errorMailInterval = (1 * 60 * 60 * 1000); // 1 hour
|
|
113
|
+
|
|
88
114
|
const logFields = [
|
|
89
115
|
'date',
|
|
90
116
|
'logLevelId',
|
|
@@ -111,13 +137,14 @@ export default class Console extends Service<Config, Hooks, Application> {
|
|
|
111
137
|
|
|
112
138
|
// Services
|
|
113
139
|
public logger!: Logger;
|
|
114
|
-
public bugReport = new BugReporter(this);
|
|
115
140
|
|
|
116
141
|
// Buffers
|
|
117
142
|
public logs: TLog[] = [];
|
|
118
143
|
public clients: TGuestLogs[] = [];
|
|
119
144
|
public requests: TRequestLogs[] = [];
|
|
120
145
|
public sqlQueries: TQueryLogs[] = [];
|
|
146
|
+
// Bug ID => Timestamp latest send
|
|
147
|
+
private sentBugs: {[bugId: string]: number} = {};
|
|
121
148
|
|
|
122
149
|
// Adapters
|
|
123
150
|
public log = console.log;
|
|
@@ -162,7 +189,7 @@ export default class Console extends Service<Config, Hooks, Application> {
|
|
|
162
189
|
setInterval(() => this.clean(), 60000);
|
|
163
190
|
|
|
164
191
|
// Send email report
|
|
165
|
-
this.app.on('error',
|
|
192
|
+
this.app.on('error', this.createBugReport.bind(this));
|
|
166
193
|
}
|
|
167
194
|
|
|
168
195
|
private clean() {
|
|
@@ -173,6 +200,61 @@ export default class Console extends Service<Config, Hooks, Application> {
|
|
|
173
200
|
- LOGGING
|
|
174
201
|
----------------------------------*/
|
|
175
202
|
|
|
203
|
+
public async createBugReport( error: Error, request?: ServerRequest ) {
|
|
204
|
+
|
|
205
|
+
// Print the error so it's accessible via logs
|
|
206
|
+
if (error instanceof SqlError) {
|
|
207
|
+
let printedQuery: string;
|
|
208
|
+
try {
|
|
209
|
+
printedQuery = this.printSql( error.query );
|
|
210
|
+
} catch (error) {
|
|
211
|
+
printedQuery = 'Failed to print query:' + (error || 'unknown error');
|
|
212
|
+
}
|
|
213
|
+
console.error(`Error caused by this query:`, printedQuery);
|
|
214
|
+
}
|
|
215
|
+
console.error(LogPrefix, `Sending bug report for the following error:`, error);
|
|
216
|
+
|
|
217
|
+
// Prevent spamming the mailbox if infinite loop
|
|
218
|
+
const bugId = ['server', request?.user?.name, undefined, error.message].filter(e => !!e).join('::');
|
|
219
|
+
const lastSending = this.sentBugs[bugId];
|
|
220
|
+
this.sentBugs[bugId] = Date.now();
|
|
221
|
+
const shouldSendReport = lastSending === undefined || lastSending < Date.now() - errorMailInterval;
|
|
222
|
+
if (!shouldSendReport)
|
|
223
|
+
return;
|
|
224
|
+
|
|
225
|
+
// Get context
|
|
226
|
+
const now = new Date();
|
|
227
|
+
const hash = uuid();
|
|
228
|
+
const { channelType, channelId } = this.getChannel();
|
|
229
|
+
|
|
230
|
+
// On envoi l'email avant l'insertion dans bla bdd
|
|
231
|
+
// Car cette denrière a plus de chances de provoquer une erreur
|
|
232
|
+
const logsHtml = this.printHtml(
|
|
233
|
+
this.logs.filter(e => e.channelId === channelId),
|
|
234
|
+
true
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const bugReport: ServerBug = {
|
|
238
|
+
|
|
239
|
+
type: 'server',
|
|
240
|
+
|
|
241
|
+
// Context
|
|
242
|
+
hash: hash,
|
|
243
|
+
date: now,
|
|
244
|
+
channelType,
|
|
245
|
+
channelId,
|
|
246
|
+
// User
|
|
247
|
+
user: request?.user?.email,
|
|
248
|
+
ip: request?.ip,
|
|
249
|
+
// Error
|
|
250
|
+
error,
|
|
251
|
+
stacktrace: error.stack || error.message,
|
|
252
|
+
logs: logsHtml
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
await this.runHook('bugReport', bugReport);
|
|
256
|
+
}
|
|
257
|
+
|
|
176
258
|
public getChannel() {
|
|
177
259
|
return context.getStore() || {
|
|
178
260
|
channelType: 'master',
|