5htp-core 0.2.4-3 → 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 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-3",
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",
@@ -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 === '+' ? 'txtSuccess' : 'txtError');
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,6 @@
1
+ #internalLayout {
2
+ min-height: 80vh;
3
+ align-items: center;
4
+ justify-content: center;
5
+ display: flex;
6
+ }
@@ -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/button';
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, {}, ({ message, modal }) => {
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
- <i src="times-circle" class="txtPrimary xxl" />
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 request</h1>
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/button';
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, { }, ({ api, toast, modal, request, page }) => {
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/button';
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, {}, ({ message, modal }) => {
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
- <i src="times-circle" class="txtPrimary xxl" />
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/button';
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, {}, ({ message, modal }) => {
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
- <i src="times-circle" class="txtPrimary xxl" />
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/button';
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, {}, ({ message }) => {
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
- <i src="times-circle" class="txtPrimary xxl" />
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're not allowed to access to this resource.";
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
- layout = {
55
+ return {
57
56
  path: routeOptions.layout,
58
57
  Component: layouts[routeOptions.layout]
59
58
  }
60
-
61
- } else {
59
+ }
62
60
 
63
- // Automatic layout via the nearest _layout folder
64
- for (const layoutPath in layouts)
65
- if (
66
- // The layout is nammed index when it's at the root (@/client/pages/_layout)
67
- layoutPath === 'index'
68
- // Exact match
69
- || chunkId === layoutPath
70
- // Parent
71
- || chunkId.startsWith( layoutPath + '_' )
72
- )
73
- layout = {
74
- path: layoutPath,
75
- Component: layouts[layoutPath]
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
- return layout;
76
+ // Internal layout
77
+ return {
78
+ path: '/',
79
+ Component: internalLayout
80
+ }
82
81
  }
@@ -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', (error: Error, request?: ServerRequest) => this.bugReport.server(error, request));
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',