5htp-core 0.2.0 → 0.2.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/package.json +3 -3
- package/src/client/app/component.tsx +0 -3
- package/src/client/assets/css/components.less +52 -0
- package/src/client/assets/css/core.less +7 -28
- package/src/client/assets/css/theme.less +1 -1
- package/src/client/assets/css/{borders.less → utils/borders.less} +0 -0
- package/src/client/assets/css/{layouts.less → utils/layouts.less} +0 -0
- package/src/client/assets/css/{medias.less → utils/medias.less} +0 -1
- package/src/client/assets/css/{sizing.less → utils/sizing.less} +0 -0
- package/src/client/assets/css/{spacing.less → utils/spacing.less} +0 -0
- package/src/client/components/Card/index.tsx +11 -5
- package/src/client/components/Dialog/Manager.tsx +3 -3
- package/src/client/components/Dialog/index.less +2 -4
- package/src/client/components/Row/index.less +0 -2
- package/src/client/components/Table/index.tsx +3 -2
- package/src/client/components/containers/champs.less +0 -2
- package/src/client/components/index.ts +16 -1
- package/src/client/components/input/BaseV2/index.less +0 -2
- package/src/client/components/input/Date/index.less +0 -2
- package/src/client/components/input/Periode/index.less +0 -2
- package/src/client/components/input/Radio/index.less +0 -2
- package/src/client/components/input/UploadImage/index.less +0 -2
- package/src/client/services/router/components/Page.tsx +4 -4
- package/src/client/services/router/components/router.tsx +11 -2
- package/src/client/services/router/index.tsx +11 -6
- package/src/client/services/router/request/api.ts +22 -24
- package/src/client/services/router/response/index.tsx +1 -1
- package/src/client/services/router/response/page.ts +9 -14
- package/src/common/router/request/api.ts +1 -1
- package/src/common/router/response/page.ts +9 -1
- package/src/common/validation/schema.ts +1 -0
- package/src/common/validation/validator.ts +13 -6
- package/src/server/services/console/bugReporter.ts +1 -1
- package/src/server/services/database/connection.ts +12 -10
- package/src/server/{error/index.ts → services/database/debug.ts} +7 -0
- package/src/server/services/email/index.ts +13 -21
- package/src/server/services/email/transporter.ts +38 -0
- package/src/server/services/router/index.ts +8 -7
- package/src/server/services/router/request/api.ts +9 -6
- package/src/server/services/router/response/index.ts +8 -3
- package/src/server/services/users/index.ts +1 -1
- package/src/server/{data → services_old}/SocketClient.ts +0 -0
- package/src/server/{data/Token.olg.ts → services_old/Token.old.ts} +0 -0
- package/src/server/{data → services_old}/aes.ts +0 -0
- package/src/client/assets/css/components/components.less +0 -31
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "5htp-core",
|
|
3
|
-
"description": "Convenient
|
|
4
|
-
"version": "0.2.
|
|
3
|
+
"description": "Convenient TypeScript framework designed for Performance and Productivity.",
|
|
4
|
+
"version": "0.2.1-1",
|
|
5
5
|
"author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
|
|
6
6
|
"repository": "git://github.com/gaetanlegac/5htp-core.git",
|
|
7
7
|
"license": "MIT",
|
|
@@ -84,6 +84,6 @@
|
|
|
84
84
|
"@types/universal-analytics": "^0.4.5",
|
|
85
85
|
"@types/webpack-env": "^1.16.2",
|
|
86
86
|
"@types/ws": "^7.4.7",
|
|
87
|
-
"babel-plugin-glob-import": "^0.0.
|
|
87
|
+
"babel-plugin-glob-import": "^0.0.6-2"
|
|
88
88
|
}
|
|
89
89
|
}
|
|
@@ -14,9 +14,6 @@ import DialogManager from '@client/components/Dialog/Manager'
|
|
|
14
14
|
import Router from '@client/services/router/components/router';
|
|
15
15
|
import type { TClientOrServerContext } from '@common/router';
|
|
16
16
|
|
|
17
|
-
// Resources
|
|
18
|
-
import "@client/assets/css/core.less";
|
|
19
|
-
|
|
20
17
|
/*----------------------------------
|
|
21
18
|
- COMPOSANT
|
|
22
19
|
----------------------------------*/
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Text
|
|
2
|
+
@import './text/text.less';
|
|
3
|
+
@import './text/icons.less';
|
|
4
|
+
@import './text/icons.less';
|
|
5
|
+
@import './text/titres.less';
|
|
6
|
+
|
|
7
|
+
// Components
|
|
8
|
+
@import './components/input.less';
|
|
9
|
+
@import './components/button.less';
|
|
10
|
+
@import './components/lists.less';
|
|
11
|
+
@import './components/card.less';
|
|
12
|
+
@import './components/logo.less';
|
|
13
|
+
@import './components/table.less';
|
|
14
|
+
@import './components/other.less';
|
|
15
|
+
@import '@client/components/chart/chart.less';
|
|
16
|
+
@import '@client/components/data/progressbar/index.less';
|
|
17
|
+
|
|
18
|
+
// Les classes utilitaires override les classes de composant
|
|
19
|
+
@import './utils/borders.less';
|
|
20
|
+
@import './utils/layouts.less';
|
|
21
|
+
|
|
22
|
+
.white-card() {
|
|
23
|
+
background: white;
|
|
24
|
+
box-shadow: 0 3px 2px fade(#000, 10%);
|
|
25
|
+
border: solid 1px fade(#000, 10%);
|
|
26
|
+
border-radius: @radius;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.card,
|
|
30
|
+
.input.text,
|
|
31
|
+
.btn,
|
|
32
|
+
.table,
|
|
33
|
+
i.solid {
|
|
34
|
+
|
|
35
|
+
.build-theme-bg( #fff, #8E8E8E);
|
|
36
|
+
|
|
37
|
+
&:not(.bg) {
|
|
38
|
+
.white-card();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.bg & {
|
|
42
|
+
box-shadow: none;
|
|
43
|
+
border: none;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
//.card.clickable:hover,
|
|
48
|
+
.btn:hover,
|
|
49
|
+
.input.text.focus {
|
|
50
|
+
z-index: 5;
|
|
51
|
+
box-shadow: 0 10px 50px fade(#000, 15%);
|
|
52
|
+
}
|
|
@@ -1,35 +1,14 @@
|
|
|
1
|
+
// Utils
|
|
2
|
+
@import './utils/medias.less';
|
|
3
|
+
@import './utils/sizing.less';
|
|
4
|
+
@import './utils/spacing.less';
|
|
5
|
+
@import (reference) "./theme.less";
|
|
6
|
+
|
|
7
|
+
// Fonts
|
|
1
8
|
@import '../fonts/Inter/index.less';
|
|
2
9
|
@import '../fonts/Rubik/index.less';
|
|
3
10
|
@import '../fonts/Lato/index.less';
|
|
4
11
|
|
|
5
|
-
@import './text/text.less';
|
|
6
|
-
@import './text/icons.less';
|
|
7
|
-
@import './text/icons.less';
|
|
8
|
-
@import './text/titres.less';
|
|
9
|
-
|
|
10
|
-
@import './components/components.less';
|
|
11
|
-
@import './components/input.less';
|
|
12
|
-
@import './components/button.less';
|
|
13
|
-
@import './components/lists.less';
|
|
14
|
-
@import './components/card.less';
|
|
15
|
-
@import './components/logo.less';
|
|
16
|
-
@import './components/table.less';
|
|
17
|
-
@import './components/other.less';
|
|
18
|
-
|
|
19
|
-
// Les classes utilitaires override les classes de composant
|
|
20
|
-
@import './borders.less';
|
|
21
|
-
@import './spacing.less';
|
|
22
|
-
@import './layouts.less';
|
|
23
|
-
|
|
24
|
-
@import './medias.less';
|
|
25
|
-
@import './sizing.less';
|
|
26
|
-
|
|
27
|
-
@import '@client/components/chart/chart.less';
|
|
28
|
-
@import '@client/components/data/progressbar/index.less';
|
|
29
|
-
|
|
30
|
-
@import (reference) "./theme.less";
|
|
31
|
-
@import "~@/client/assets/theme.less";
|
|
32
|
-
|
|
33
12
|
// Apply the theme class
|
|
34
13
|
.bg {
|
|
35
14
|
background: var(--cBg);
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -7,7 +7,7 @@ import React from 'react';
|
|
|
7
7
|
import type { ComponentChild } from 'preact';
|
|
8
8
|
|
|
9
9
|
// Core components
|
|
10
|
-
import
|
|
10
|
+
import { Logo } from '@client/components';
|
|
11
11
|
import { Link } from '@client/services/router';
|
|
12
12
|
|
|
13
13
|
// Resources
|
|
@@ -28,6 +28,7 @@ export type Props = {
|
|
|
28
28
|
link?: string,
|
|
29
29
|
cover?: {
|
|
30
30
|
color?: string,
|
|
31
|
+
image?: string,
|
|
31
32
|
title?: string,
|
|
32
33
|
logo?: ComponentChild
|
|
33
34
|
},
|
|
@@ -49,10 +50,15 @@ export default ({ title, link, cover, metas, class: className = '' }: Props) =>
|
|
|
49
50
|
|
|
50
51
|
{cover && (
|
|
51
52
|
<header class="bg img row al-left cover pdb-1" style={{
|
|
52
|
-
backgroundColor: cover.color
|
|
53
|
+
backgroundColor: cover.color,
|
|
54
|
+
backgroundImage: cover.image
|
|
55
|
+
? 'url(' + cover.image + ')'
|
|
56
|
+
: undefined
|
|
53
57
|
}}>
|
|
54
58
|
|
|
55
|
-
{cover
|
|
59
|
+
{typeof cover.logo === 'string'
|
|
60
|
+
? <Logo src={cover.logo} size="xl" />
|
|
61
|
+
: cover.logo}
|
|
56
62
|
|
|
57
63
|
{cover.title && (
|
|
58
64
|
<strong>
|
|
@@ -72,9 +78,9 @@ export default ({ title, link, cover, metas, class: className = '' }: Props) =>
|
|
|
72
78
|
)}
|
|
73
79
|
|
|
74
80
|
{metas && (
|
|
75
|
-
<ul class="row fill">
|
|
81
|
+
<ul class="row fill al-top">
|
|
76
82
|
{metas.map(({ label, value, class: className }) => (
|
|
77
|
-
<li class={"col al-left sp-05"}>
|
|
83
|
+
<li class={"col al-left txt-left sp-05"}>
|
|
78
84
|
{label}
|
|
79
85
|
<strong class={className}>{value}</strong>
|
|
80
86
|
</li>
|
|
@@ -163,9 +163,9 @@ export const createDialog = (app: Application, isToast: boolean): DialogActions
|
|
|
163
163
|
setToasts: undefined as unknown as DialogActions["setToasts"],
|
|
164
164
|
|
|
165
165
|
confirm: (title: string, content: string | ComponentChild, defaultBtn: 'Yes'|'No' = 'No') => show<boolean>(({ close }) => (
|
|
166
|
-
<div class="col">
|
|
166
|
+
<div class="card col">
|
|
167
167
|
<header>
|
|
168
|
-
<
|
|
168
|
+
<h2>{title}</h2>
|
|
169
169
|
</header>
|
|
170
170
|
{typeof content === 'string' ? <p>{content}</p> : content}
|
|
171
171
|
<footer class="row fill">
|
|
@@ -181,7 +181,7 @@ export const createDialog = (app: Application, isToast: boolean): DialogActions
|
|
|
181
181
|
</div>
|
|
182
182
|
)),
|
|
183
183
|
|
|
184
|
-
loading: (title: string) => app.
|
|
184
|
+
loading: (title: string) => app.loading = show({
|
|
185
185
|
title: title,
|
|
186
186
|
type: 'loading'
|
|
187
187
|
}),
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
@import (reference) "~@/client/assets/theme.less";
|
|
2
|
-
|
|
3
1
|
@toast-zindex: 999;
|
|
4
2
|
|
|
5
3
|
#dialog {
|
|
@@ -89,10 +87,10 @@
|
|
|
89
87
|
border-radius: @radius;
|
|
90
88
|
|
|
91
89
|
// Desktop = vertically center the modal
|
|
92
|
-
|
|
90
|
+
@media (min-width: 900px) {
|
|
93
91
|
justify-content: center;
|
|
94
92
|
padding: @spacing;
|
|
95
|
-
}
|
|
93
|
+
}
|
|
96
94
|
|
|
97
95
|
// Pour les animations (ex: conffetis
|
|
98
96
|
> canvas {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
|
|
1
2
|
/*----------------------------------
|
|
2
3
|
- DEPENDANCES
|
|
3
4
|
----------------------------------*/
|
|
@@ -22,7 +23,7 @@ export type Props<TRow> = {
|
|
|
22
23
|
columns: (row: TRow, rows: TRow[], index: number) => TColumn[];
|
|
23
24
|
|
|
24
25
|
setData?: (rows: TRow[]) => void,
|
|
25
|
-
|
|
26
|
+
empty?: ComponentChild,
|
|
26
27
|
className?: string,
|
|
27
28
|
|
|
28
29
|
actions?: TAction<TRow>[]
|
|
@@ -39,7 +40,7 @@ export type TColumn = {
|
|
|
39
40
|
- COMPOSANTS
|
|
40
41
|
----------------------------------*/
|
|
41
42
|
export default function Liste<TRow extends TDonneeInconnue>({
|
|
42
|
-
data: rows, setData,
|
|
43
|
+
data: rows, setData, empty ,
|
|
43
44
|
columns, actions, ...props
|
|
44
45
|
}: Props<TRow>) {
|
|
45
46
|
|
|
@@ -5,4 +5,19 @@ export { default as Table } from './Table';
|
|
|
5
5
|
export { default as Select } from './Select';
|
|
6
6
|
export { default as Amount } from './Amount';
|
|
7
7
|
export { default as Logo } from './Logo';
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
export { default as Input } from './input';
|
|
10
|
+
export { default as Textarea } from './input/Textarea';
|
|
11
|
+
export { default as Number } from './input/Number';
|
|
12
|
+
export { default as Slider } from './input/Slider';
|
|
13
|
+
export { default as Upload } from './input/Upload';
|
|
14
|
+
export { default as Radio } from './input/Radio';
|
|
15
|
+
|
|
16
|
+
// TOD: fix popover component
|
|
17
|
+
//export { default as Date } from './input/Date';
|
|
18
|
+
//export { default as Periode } from './input/Periode';
|
|
19
|
+
|
|
20
|
+
// TODO: adapt
|
|
21
|
+
//export { default as Couleur } from './input/Couleur';
|
|
22
|
+
//export { default as Code } from './input/Code';
|
|
23
|
+
//export { default as Rte } from './input/Rte';
|
|
@@ -19,20 +19,20 @@ export default ({ page, isCurrent }: { page: Page, isCurrent?: boolean }) => {
|
|
|
19
19
|
const context = useContext();
|
|
20
20
|
|
|
21
21
|
const [apiData, setApiData] = React.useState<{[k: string]: any} | null>(
|
|
22
|
-
page.
|
|
22
|
+
page.loading ? null : page.data
|
|
23
23
|
);
|
|
24
24
|
page.setAllData = setApiData;
|
|
25
25
|
|
|
26
26
|
React.useEffect(() => {
|
|
27
27
|
|
|
28
28
|
// Fetch the data asynchronously for the first time
|
|
29
|
-
if (apiData === null && isCurrent)
|
|
29
|
+
if (/*apiData === null && */isCurrent)
|
|
30
30
|
page.fetchData().then( loadedData => {
|
|
31
|
-
page.
|
|
31
|
+
page.loading = false;
|
|
32
32
|
setApiData(loadedData);
|
|
33
33
|
})
|
|
34
34
|
|
|
35
|
-
}, []);
|
|
35
|
+
}, [page]);
|
|
36
36
|
|
|
37
37
|
return (
|
|
38
38
|
<div
|
|
@@ -62,8 +62,9 @@ export default ({ service: router }: { service: Router }) => {
|
|
|
62
62
|
return;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
// Set.
|
|
66
|
-
newpage.
|
|
65
|
+
// Set.loading state
|
|
66
|
+
newpage.isLoading = true;
|
|
67
|
+
newpage.loading = <i src="spin" />
|
|
67
68
|
// Add page container
|
|
68
69
|
setPages( pages => {
|
|
69
70
|
|
|
@@ -79,7 +80,15 @@ export default ({ service: router }: { service: Router }) => {
|
|
|
79
80
|
const curLayout = currentRoute?.options.layout;
|
|
80
81
|
const newLayout = newpage?.route.options.layout;
|
|
81
82
|
if (newLayout && curLayout && newLayout.path !== curLayout.path) {
|
|
83
|
+
|
|
84
|
+
// TEMPORARY FIX: reload everything when we change layout
|
|
85
|
+
// Because layout can have a different theme
|
|
86
|
+
// But when we call setLayout, the style of the previous layout are still oaded and applied
|
|
87
|
+
// Find a way to unload the previous layout / page resources before to load the new one
|
|
82
88
|
console.log(LogPrefix, `Changing layout. Before:`, curLayout, 'New layout:', newLayout);
|
|
89
|
+
window.location.replace(request.path);
|
|
90
|
+
return pages;
|
|
91
|
+
|
|
83
92
|
context.app.setLayout(newLayout);
|
|
84
93
|
}
|
|
85
94
|
|
|
@@ -343,6 +343,7 @@ export default class ClientRouter<
|
|
|
343
343
|
const request = new ClientRequest(location, this);
|
|
344
344
|
|
|
345
345
|
// Restituate SSR response
|
|
346
|
+
let apiData: {} = {}
|
|
346
347
|
if (this.ssrData) {
|
|
347
348
|
|
|
348
349
|
console.log("SSR Response restitution ...");
|
|
@@ -350,11 +351,15 @@ export default class ClientRouter<
|
|
|
350
351
|
request.user = this.ssrData.user || null;
|
|
351
352
|
|
|
352
353
|
request.data = this.ssrData.request.data;
|
|
354
|
+
|
|
355
|
+
apiData = this.ssrData.page.data || {};
|
|
353
356
|
}
|
|
354
357
|
|
|
355
|
-
|
|
358
|
+
// Replacer api data par ssr data
|
|
359
|
+
|
|
360
|
+
const response = await this.createResponse(route, request, apiData)
|
|
356
361
|
|
|
357
|
-
ReactDOM.hydrate(<App context={response.context} />, document.body, () => {
|
|
362
|
+
ReactDOM.hydrate( <App context={response.context} />, document.body, () => {
|
|
358
363
|
|
|
359
364
|
console.log(`Render complete`);
|
|
360
365
|
|
|
@@ -364,7 +369,7 @@ export default class ClientRouter<
|
|
|
364
369
|
private async createResponse(
|
|
365
370
|
route: TUnresolvedRoute | TRoute,
|
|
366
371
|
request: ClientRequest<this>,
|
|
367
|
-
|
|
372
|
+
pageData: {} = {}
|
|
368
373
|
): Promise<ClientPage> {
|
|
369
374
|
|
|
370
375
|
// Load if not done before
|
|
@@ -376,7 +381,7 @@ export default class ClientRouter<
|
|
|
376
381
|
try {
|
|
377
382
|
|
|
378
383
|
const response = new ClientResponse<this, ClientPage>(request, route);
|
|
379
|
-
return await response.runController(
|
|
384
|
+
return await response.runController(pageData);
|
|
380
385
|
|
|
381
386
|
} catch (error) {
|
|
382
387
|
|
|
@@ -387,7 +392,7 @@ export default class ClientRouter<
|
|
|
387
392
|
private async createErrorResponse(
|
|
388
393
|
e: any,
|
|
389
394
|
request: ClientRequest<this>,
|
|
390
|
-
|
|
395
|
+
pageData: {} = {}
|
|
391
396
|
): Promise<ClientPage> {
|
|
392
397
|
|
|
393
398
|
const code = 'http' in e ? e.http : 500;
|
|
@@ -406,7 +411,7 @@ export default class ClientRouter<
|
|
|
406
411
|
route = this.errors[code] = await this.load(route);
|
|
407
412
|
|
|
408
413
|
const response = new ClientResponse<this, ClientPage>(request, route);
|
|
409
|
-
return await response.runController(
|
|
414
|
+
return await response.runController(pageData);
|
|
410
415
|
}
|
|
411
416
|
|
|
412
417
|
/*----------------------------------
|
|
@@ -127,35 +127,33 @@ export default class ApiClient implements ApiClientService {
|
|
|
127
127
|
})
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
public async fetchSync(fetchers: TFetcherList): Promise<TObjetDonnees> {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
[
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const fetchersArgs: {[id: string]: TFetcherArgs} = {};
|
|
142
|
-
for (const id in fetchers)
|
|
143
|
-
fetchersArgs[id] = [
|
|
144
|
-
fetchers[id].method,
|
|
145
|
-
fetchers[id].path,
|
|
146
|
-
fetchers[id].data,
|
|
147
|
-
]
|
|
130
|
+
public async fetchSync(fetchers: TFetcherList, alreadyLoadedData: {}): Promise<TObjetDonnees> {
|
|
131
|
+
|
|
132
|
+
// Pick the fetchers where the data is needed
|
|
133
|
+
const fetchersToRun: TFetcherList = {};
|
|
134
|
+
let fetchersCount: number = 0;
|
|
135
|
+
for (const fetcherId in fetchers)
|
|
136
|
+
if (!( fetcherId in alreadyLoadedData )) {
|
|
137
|
+
fetchersToRun[ fetcherId ] = fetchers[ fetcherId ]
|
|
138
|
+
fetchersCount++;
|
|
139
|
+
}
|
|
148
140
|
|
|
149
|
-
|
|
141
|
+
// Fetch all the api data thanks to one http request
|
|
142
|
+
const fetchedData = fetchersCount === 0
|
|
143
|
+
? 0
|
|
144
|
+
: await this.fetch("POST", "/api", {
|
|
145
|
+
fetchers: fetchersToRun
|
|
146
|
+
}).then((res) => {
|
|
150
147
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
148
|
+
const data: TObjetDonnees = {};
|
|
149
|
+
for (const id in res)
|
|
150
|
+
data[id] = res[id];
|
|
154
151
|
|
|
155
|
-
|
|
152
|
+
return data;
|
|
156
153
|
|
|
157
|
-
|
|
154
|
+
});
|
|
158
155
|
|
|
156
|
+
return { ...alreadyLoadedData, ...fetchedData }
|
|
159
157
|
}
|
|
160
158
|
|
|
161
159
|
public configure = (...[method, path, data, options]: TFetcherArgs): AxiosRequestConfig => {
|
|
@@ -94,7 +94,7 @@ export default class ClientPageResponse<
|
|
|
94
94
|
|
|
95
95
|
// Default data type for `return <raw data>`
|
|
96
96
|
if (result instanceof ClientPage)
|
|
97
|
-
await result.
|
|
97
|
+
await result.preRender(additionnalData);
|
|
98
98
|
else
|
|
99
99
|
throw new Error(`Unsupported response format: ${result.constructor?.name}`);
|
|
100
100
|
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
- DEPENDANCES
|
|
3
3
|
----------------------------------*/
|
|
4
4
|
|
|
5
|
+
// Npm
|
|
6
|
+
import type { ComponentChild } from 'preact';
|
|
7
|
+
|
|
5
8
|
// Core
|
|
6
9
|
import type { TClientOrServerContext } from '@common/router';
|
|
7
10
|
import PageResponse, { TDataProvider, TFrontRenderer } from "@common/router/response/page";
|
|
@@ -22,6 +25,7 @@ import type ClientRouter from '..';
|
|
|
22
25
|
export default class ClientPage<TRouter = ClientRouter> extends PageResponse<TRouter> {
|
|
23
26
|
|
|
24
27
|
public isLoading: boolean = false;
|
|
28
|
+
public loading: false | ComponentChild;
|
|
25
29
|
public scrollToId: string;
|
|
26
30
|
|
|
27
31
|
public constructor(
|
|
@@ -38,23 +42,15 @@ export default class ClientPage<TRouter = ClientRouter> extends PageResponse<TRo
|
|
|
38
42
|
this.scrollToId = context.request.hash;
|
|
39
43
|
}
|
|
40
44
|
|
|
41
|
-
public async
|
|
45
|
+
public async preRender( data?: TObjetDonnees ) {
|
|
42
46
|
|
|
43
47
|
// Add the page to the context
|
|
44
48
|
this.context.page = this;
|
|
45
|
-
|
|
46
|
-
// Load the fetchers list to load data if needed
|
|
47
|
-
if (this.dataProvider)
|
|
48
|
-
this.fetchers = this.dataProvider( this.context );
|
|
49
|
+
this.isLoading = true;
|
|
49
50
|
|
|
50
51
|
// Data succesfully loaded
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
this.data = data;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Fetch data
|
|
57
|
-
this.data = await this.fetchData();
|
|
52
|
+
this.data = data || await this.fetchData();
|
|
53
|
+
this.isLoading = false;
|
|
58
54
|
|
|
59
55
|
return this;
|
|
60
56
|
}
|
|
@@ -81,8 +77,7 @@ export default class ClientPage<TRouter = ClientRouter> extends PageResponse<TRo
|
|
|
81
77
|
}));
|
|
82
78
|
}
|
|
83
79
|
|
|
84
|
-
public
|
|
85
|
-
public loading(state: boolean) {
|
|
80
|
+
public setLoading(state: boolean) {
|
|
86
81
|
|
|
87
82
|
if (state === true) {
|
|
88
83
|
if (!document.body.classList.contains("loading"))
|
|
@@ -68,5 +68,5 @@ export default abstract class ApiClient {
|
|
|
68
68
|
|
|
69
69
|
public abstract createFetcher<TData extends unknown = unknown>(...args: TFetcherArgs): TFetcher<TData>;
|
|
70
70
|
|
|
71
|
-
public abstract fetchSync(fetchers: TFetcherList): Promise<TObjetDonnees>;
|
|
71
|
+
public abstract fetchSync(fetchers: TFetcherList, alreadyLoadedData: {}): Promise<TObjetDonnees>;
|
|
72
72
|
}
|
|
@@ -75,6 +75,14 @@ export default class PageResponse<TRouter extends ClientOrServerRouter = ClientO
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
public async fetchData() {
|
|
78
|
-
|
|
78
|
+
|
|
79
|
+
// Load the fetchers list to load data if needed
|
|
80
|
+
if (this.dataProvider)
|
|
81
|
+
this.fetchers = this.dataProvider({ ...this.context, ...this.context.request.data });
|
|
82
|
+
|
|
83
|
+
// Execute the fetchers for missing data
|
|
84
|
+
console.log(`[router][page] Fetching api data:` + Object.keys(this.fetchers));
|
|
85
|
+
this.data = await this.context.request.api.fetchSync( this.fetchers, this.data );
|
|
86
|
+
return this.data;
|
|
79
87
|
}
|
|
80
88
|
}
|
|
@@ -28,6 +28,7 @@ export type TValidationResult<TFields extends TSchemaFields> = {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export type TValidatedData<TFields extends TSchemaFields> = {
|
|
31
|
+
// For each field, the values returned by validator.validate()
|
|
31
32
|
[name in keyof TFields]: ReturnType<TFields[name]["validate"]>
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -46,7 +46,14 @@ type TValidationArgs<TValue, TAllValues extends {}> = [
|
|
|
46
46
|
|
|
47
47
|
type TValidationFunction<TValue, TAllValues extends {} = {}> = (
|
|
48
48
|
...args: TValidationArgs<TValue, TAllValues>
|
|
49
|
-
) => TValue | typeof EXCLUDE_VALUE
|
|
49
|
+
) => TValue | typeof EXCLUDE_VALUE;
|
|
50
|
+
|
|
51
|
+
type TValidateReturnType<
|
|
52
|
+
TOptions extends TValidator<TValue>,
|
|
53
|
+
TValue extends any
|
|
54
|
+
> = TOptions extends { opt: true }
|
|
55
|
+
? (undefined | TValue)
|
|
56
|
+
: TValue
|
|
50
57
|
|
|
51
58
|
/*----------------------------------
|
|
52
59
|
- CONST
|
|
@@ -57,32 +64,32 @@ export const EXCLUDE_VALUE = "action:exclure" as const;
|
|
|
57
64
|
/*----------------------------------
|
|
58
65
|
- CLASS
|
|
59
66
|
----------------------------------*/
|
|
60
|
-
export default class Validator<TValue> {
|
|
67
|
+
export default class Validator<TValue, TOptions extends TValidator<TValue> = TValidator<TValue>> {
|
|
61
68
|
|
|
62
69
|
public constructor(
|
|
63
70
|
public type: string,
|
|
64
71
|
public validateType: TValidationFunction<TValue>,
|
|
65
|
-
public options:
|
|
72
|
+
public options: TOptions
|
|
66
73
|
) {
|
|
67
74
|
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
public isEmpty = (val: any) => val === undefined || val === '' || val === null
|
|
71
78
|
|
|
72
|
-
public validate(...[ val, input, output, correct ]: TValidationArgs<TValue, {}>) {
|
|
79
|
+
public validate(...[ val, input, output, correct ]: TValidationArgs<TValue, {}>): TValidateReturnType<TOptions, TValue> {
|
|
73
80
|
|
|
74
81
|
// Required value
|
|
75
82
|
if (this.isEmpty(val)) {
|
|
76
83
|
// Optionnel, on skip
|
|
77
84
|
if (this.options.opt === true)
|
|
78
|
-
return undefined
|
|
85
|
+
return undefined as TValidateReturnType<TOptions, TValue>;
|
|
79
86
|
// Requis
|
|
80
87
|
else
|
|
81
88
|
throw new InputError("Please enter a value");
|
|
82
89
|
}
|
|
83
90
|
|
|
84
91
|
// Validate type
|
|
85
|
-
return this.validateType(val, input, output, correct)
|
|
92
|
+
return this.validateType(val, input, output, correct) as TValidateReturnType<TOptions, TValue>;
|
|
86
93
|
}
|
|
87
94
|
|
|
88
95
|
}
|
|
@@ -7,12 +7,12 @@ import mysql from 'mysql2/promise';
|
|
|
7
7
|
|
|
8
8
|
// Core: general
|
|
9
9
|
import Application from '@server/app';
|
|
10
|
-
import { SqlError } from '@server/error';
|
|
11
10
|
import Service from '@server/app/service';
|
|
12
11
|
|
|
13
12
|
// Core: specific
|
|
13
|
+
import { SqlError } from './debug';
|
|
14
14
|
import type Console from '../console';
|
|
15
|
-
import MetadataParser, { TDatabasesList, TMetasTable, TColumnTypes } from './metas';
|
|
15
|
+
import MetadataParser, { TDatabasesList, TMetasTable, TColumnTypes, TMetasColonne } from './metas';
|
|
16
16
|
import { TMySQLTypeName, mysqlToJs, js as jsTypes } from './datatypes';
|
|
17
17
|
import Bucket from './bucket';
|
|
18
18
|
|
|
@@ -157,7 +157,7 @@ export default class DatabaseConnection extends Service<DatabaseServiceConfig, T
|
|
|
157
157
|
return next();
|
|
158
158
|
|
|
159
159
|
// Normal column
|
|
160
|
-
let
|
|
160
|
+
let databaseColumn: TMetasColonne | undefined;
|
|
161
161
|
if (field.db && field.table && field.name) {
|
|
162
162
|
|
|
163
163
|
const db = this.tables[ field.db ];
|
|
@@ -172,15 +172,17 @@ export default class DatabaseConnection extends Service<DatabaseServiceConfig, T
|
|
|
172
172
|
throw new Error(`Table metadatas for ${field.db}.${field.table} were not loaded.`);
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
175
|
+
databaseColumn = table.colonnes[field.name];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
let type: TColumnTypes;
|
|
180
|
+
if (databaseColumn !== undefined) {
|
|
180
181
|
|
|
181
|
-
type =
|
|
182
|
+
type = databaseColumn.type;
|
|
182
183
|
|
|
183
|
-
//
|
|
184
|
+
// If the column name has not been found in the concerned table,
|
|
185
|
+
// We assume it's a computed column
|
|
184
186
|
} else {
|
|
185
187
|
|
|
186
188
|
const mysqlType = {
|
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
|
|
9
9
|
// Core
|
|
10
10
|
import Application, { Service } from '@server/app';
|
|
11
|
-
import { jsonToHtml } from './utils';
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
// Speciic
|
|
13
|
+
import { jsonToHtml } from './utils';
|
|
14
|
+
import type { Transporter } from './transporter';
|
|
14
15
|
|
|
15
16
|
/*----------------------------------
|
|
16
17
|
- SERVICE CONFIG
|
|
@@ -22,7 +23,9 @@ export type Config = {
|
|
|
22
23
|
transporter: string,
|
|
23
24
|
from: string
|
|
24
25
|
},
|
|
25
|
-
transporters:
|
|
26
|
+
transporters: {
|
|
27
|
+
[transporterName: string]: Transporter
|
|
28
|
+
},
|
|
26
29
|
bugReport: {
|
|
27
30
|
from: string,
|
|
28
31
|
to: string
|
|
@@ -33,16 +36,12 @@ export type Hooks = {
|
|
|
33
36
|
|
|
34
37
|
}
|
|
35
38
|
|
|
36
|
-
declare global {
|
|
37
|
-
namespace Config {
|
|
38
|
-
interface EmailTransporters { }
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
39
|
/*----------------------------------
|
|
43
40
|
- TYPES: EMAILS
|
|
44
41
|
----------------------------------*/
|
|
45
42
|
|
|
43
|
+
export { Transporter } from './transporter';
|
|
44
|
+
|
|
46
45
|
export type TEmail = THtmlEmail | TTemplateEmail;
|
|
47
46
|
|
|
48
47
|
type TBaseEmail = {
|
|
@@ -68,11 +67,6 @@ export type TCompleteEmail = With<THtmlEmail, {
|
|
|
68
67
|
/*----------------------------------
|
|
69
68
|
- TYPES: OPTIONS
|
|
70
69
|
----------------------------------*/
|
|
71
|
-
|
|
72
|
-
export abstract class Transporter {
|
|
73
|
-
public abstract send( emails: TCompleteEmail[] ): Promise<void>;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
70
|
type TOptions = {
|
|
77
71
|
transporter?: string,
|
|
78
72
|
testing?: boolean
|
|
@@ -82,6 +76,8 @@ type TOptions = {
|
|
|
82
76
|
- FONCTIONS
|
|
83
77
|
----------------------------------*/
|
|
84
78
|
export default class Email extends Service<Config, Hooks, Application> {
|
|
79
|
+
|
|
80
|
+
private transporters = this.config.transporters;
|
|
85
81
|
|
|
86
82
|
public async register() {
|
|
87
83
|
|
|
@@ -91,11 +87,6 @@ export default class Email extends Service<Config, Hooks, Application> {
|
|
|
91
87
|
|
|
92
88
|
}
|
|
93
89
|
|
|
94
|
-
private transporters = {} as {[name: string]: Transporter};
|
|
95
|
-
public addTransporter( name: string, transporter: (new () => Transporter) ) {
|
|
96
|
-
console.log(`[email] registering email transporter: ${name}`);
|
|
97
|
-
this.transporters[ name ] = new transporter();
|
|
98
|
-
}
|
|
99
90
|
|
|
100
91
|
public async send(
|
|
101
92
|
emails: TEmail | TEmail[],
|
|
@@ -119,6 +110,7 @@ export default class Email extends Service<Config, Hooks, Application> {
|
|
|
119
110
|
: email.to;
|
|
120
111
|
|
|
121
112
|
// Via template
|
|
113
|
+
// TODO: Restore templates feature
|
|
122
114
|
if ('template' in email) {
|
|
123
115
|
|
|
124
116
|
const template = templates[email.template];
|
|
@@ -170,8 +162,8 @@ export default class Email extends Service<Config, Hooks, Application> {
|
|
|
170
162
|
return;
|
|
171
163
|
}
|
|
172
164
|
|
|
173
|
-
const
|
|
174
|
-
await
|
|
165
|
+
const transporter = this.transporters[ transporterName ];
|
|
166
|
+
await transporter.send(emailsToSend);
|
|
175
167
|
|
|
176
168
|
}
|
|
177
169
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// Npm
|
|
6
|
+
|
|
7
|
+
// Core
|
|
8
|
+
import type Application from "@server/app";
|
|
9
|
+
import type EmailService from '@server/services/email';
|
|
10
|
+
|
|
11
|
+
// Specific
|
|
12
|
+
import type { TCompleteEmail } from ".";
|
|
13
|
+
|
|
14
|
+
/*----------------------------------
|
|
15
|
+
- TYPES
|
|
16
|
+
----------------------------------*/
|
|
17
|
+
|
|
18
|
+
export type TBasicConfig = {
|
|
19
|
+
api: string,
|
|
20
|
+
debug: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/*----------------------------------
|
|
24
|
+
- CLASS
|
|
25
|
+
----------------------------------*/
|
|
26
|
+
export abstract class Transporter<TConfig extends {} = {}> {
|
|
27
|
+
|
|
28
|
+
public constructor(
|
|
29
|
+
protected app: Application & { email: EmailService },
|
|
30
|
+
protected config: TBasicConfig & TConfig,
|
|
31
|
+
|
|
32
|
+
protected email = app.email
|
|
33
|
+
) {
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public abstract send( emails: TCompleteEmail[] ): Promise<void>;
|
|
38
|
+
}
|
|
@@ -24,7 +24,7 @@ import BaseRouter, {
|
|
|
24
24
|
} from '@common/router';
|
|
25
25
|
import { buildRegex, getRegisterPageArgs } from '@common/router/register';
|
|
26
26
|
import { layoutsList } from '@common/router/layouts';
|
|
27
|
-
import {
|
|
27
|
+
import { TFetcherList, TFetcher } from '@common/router/request/api';
|
|
28
28
|
import type { TFrontRenderer } from '@common/router/response/page';
|
|
29
29
|
import type { TSsrUnresolvedRoute, TRegisterPageArgs } from '@client/services/router';
|
|
30
30
|
|
|
@@ -386,7 +386,7 @@ export default class ServerRouter<
|
|
|
386
386
|
// Bulk API Requests
|
|
387
387
|
if (request.path === '/api' && typeof request.data.fetchers === "object") {
|
|
388
388
|
|
|
389
|
-
return await this.resolveApiBatch(request);
|
|
389
|
+
return await this.resolveApiBatch(request.data.fetchers, request);
|
|
390
390
|
|
|
391
391
|
} else {
|
|
392
392
|
response = await this.resolve(request);
|
|
@@ -434,7 +434,7 @@ export default class ServerRouter<
|
|
|
434
434
|
|
|
435
435
|
public async resolve(request: ServerRequest<this>): Promise<ServerResponse<this>> {
|
|
436
436
|
|
|
437
|
-
console.info(request.ip, request.method, request.domain, request.path
|
|
437
|
+
console.info(request.ip, request.method, request.domain, request.path);
|
|
438
438
|
|
|
439
439
|
const response = new ServerResponse<this>(request);
|
|
440
440
|
|
|
@@ -475,12 +475,14 @@ export default class ServerRouter<
|
|
|
475
475
|
throw new NotFound(`The requested endpoint was not found.`);
|
|
476
476
|
}
|
|
477
477
|
|
|
478
|
-
private async resolveApiBatch( request: ServerRequest<this> ) {
|
|
478
|
+
private async resolveApiBatch( fetchers: TFetcherList, request: ServerRequest<this> ) {
|
|
479
|
+
|
|
480
|
+
// TODO: use api.fetchSync instead
|
|
479
481
|
|
|
480
482
|
const responseData: TObjetDonnees = {};
|
|
481
|
-
for (const id in
|
|
483
|
+
for (const id in fetchers) {
|
|
482
484
|
|
|
483
|
-
const
|
|
485
|
+
const { method, path, data } = fetchers[id];
|
|
484
486
|
|
|
485
487
|
const response = await this.resolve(
|
|
486
488
|
request.children(method, path, data)
|
|
@@ -489,7 +491,6 @@ export default class ServerRouter<
|
|
|
489
491
|
responseData[id] = response.data;
|
|
490
492
|
|
|
491
493
|
// TODO: merge response.headers ?
|
|
492
|
-
|
|
493
494
|
}
|
|
494
495
|
|
|
495
496
|
// Status
|
|
@@ -52,22 +52,25 @@ export default class ApiClientRequest extends RequestService implements ApiClien
|
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
public async fetchSync(fetchers: TFetcherList): Promise<TObjetDonnees> {
|
|
55
|
+
public async fetchSync(fetchers: TFetcherList, alreadyLoadedData: {}): Promise<TObjetDonnees> {
|
|
56
56
|
|
|
57
|
-
const
|
|
57
|
+
const fetchedData: TObjetDonnees = { ...alreadyLoadedData };
|
|
58
58
|
|
|
59
59
|
for (const id in fetchers) {
|
|
60
60
|
|
|
61
61
|
const { method, path, data, options } = fetchers[id];
|
|
62
|
-
|
|
63
62
|
//this.router.config.debug && console.log(`[api] Resolving from internal api`, method, path, data);
|
|
64
63
|
|
|
64
|
+
// We don't fetch the already given data
|
|
65
|
+
if (id in fetchedData)
|
|
66
|
+
continue;
|
|
67
|
+
|
|
68
|
+
// Create a children request to resolve the api data
|
|
65
69
|
const internalHeaders = { accept: 'application/json' }
|
|
66
70
|
const request = this.request.children(method, path, data, { ...internalHeaders/*, ...headers*/ });
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
fetchedData[id] = await request.router.resolve(request).then(res => res.data);
|
|
69
72
|
}
|
|
70
73
|
|
|
71
|
-
return
|
|
74
|
+
return fetchedData;
|
|
72
75
|
}
|
|
73
76
|
}
|
|
@@ -107,11 +107,16 @@ export default class ServerResponse<
|
|
|
107
107
|
if (response === undefined)
|
|
108
108
|
return;
|
|
109
109
|
|
|
110
|
-
//
|
|
111
|
-
if (response instanceof
|
|
110
|
+
// No need to process the response
|
|
111
|
+
if (response instanceof ServerResponse)
|
|
112
|
+
return;
|
|
113
|
+
// Render react page to html
|
|
114
|
+
else if (response instanceof Page)
|
|
112
115
|
await this.render(response, context, additionnalData);
|
|
116
|
+
// Return HTML
|
|
113
117
|
else if (typeof response === 'string' && this.route.options.accept === 'html')
|
|
114
118
|
await this.html(response);
|
|
119
|
+
// Return JSON
|
|
115
120
|
else
|
|
116
121
|
await this.json(response);
|
|
117
122
|
}
|
|
@@ -186,7 +191,7 @@ export default class ServerResponse<
|
|
|
186
191
|
context.page = page;
|
|
187
192
|
|
|
188
193
|
// Prepare page & fetch data
|
|
189
|
-
await page.fetchData();
|
|
194
|
+
page.data = await page.fetchData();
|
|
190
195
|
if (additionnalData !== undefined) // Example: error message for error pages
|
|
191
196
|
page.data = { ...page.data, ...additionnalData }
|
|
192
197
|
|
|
@@ -162,7 +162,7 @@ export default abstract class UsersManagementService<
|
|
|
162
162
|
|
|
163
163
|
const user = request.user;
|
|
164
164
|
|
|
165
|
-
this.config.debug && console.warn(LogPrefix, `Check auth, role = ${role}. Current user =`, user);
|
|
165
|
+
this.config.debug && console.warn(LogPrefix, `Check auth, role = ${role}. Current user =`, this.displayName(user));
|
|
166
166
|
|
|
167
167
|
if (user === undefined) {
|
|
168
168
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
.white-card() {
|
|
2
|
-
background: white;
|
|
3
|
-
box-shadow: 0 3px 2px fade(#000, 10%);
|
|
4
|
-
border: solid 1px fade(#000, 10%);
|
|
5
|
-
border-radius: @radius;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
.card,
|
|
9
|
-
.input.text,
|
|
10
|
-
.btn,
|
|
11
|
-
.table,
|
|
12
|
-
i.solid {
|
|
13
|
-
|
|
14
|
-
.build-theme-bg( #fff, #8E8E8E);
|
|
15
|
-
|
|
16
|
-
&:not(.bg) {
|
|
17
|
-
.white-card();
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
.bg & {
|
|
21
|
-
box-shadow: none;
|
|
22
|
-
border: none;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
//.card.clickable:hover,
|
|
27
|
-
.btn:hover,
|
|
28
|
-
.input.text.focus {
|
|
29
|
-
z-index: 5;
|
|
30
|
-
box-shadow: 0 10px 50px fade(#000, 15%);
|
|
31
|
-
}
|