5htp-core 0.1.1 → 0.1.2-2
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 +4 -7
- package/src/client/assets/css/components/components.less +8 -4
- package/src/client/components/Card/index.tsx +1 -1
- package/src/client/context/index.ts +3 -4
- package/src/client/index.tsx +1 -2
- package/src/client/router/Link.tsx +36 -0
- package/src/client/router/{index.tsx → index.ts} +69 -84
- package/src/client/router/route.ts +75 -0
- package/src/common/router/index.ts +6 -4
- package/src/server/app/config.ts +1 -17
- package/src/server/app/index.ts +1 -1
- package/src/server/libs/pages/document.tsx +1 -1
- package/src/server/services/auth/base.ts +27 -12
- package/src/server/services/console/bugReporter.ts +14 -1
- package/src/server/services/console/index.ts +11 -2
- package/src/server/services/email/index.ts +21 -27
- package/src/server/services/http/index.ts +14 -28
- package/src/server/services/router/index.ts +0 -0
- package/src/server/services/router/request/index.ts +0 -2
- package/src/server/services/router/request/services/auth.ts +1 -3
- package/src/server/services/router/request/services/detect.ts +2 -0
- package/src/types/aliases.d.ts +0 -11
- package/src/common/models/index.ts +0 -43
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "5htp-core",
|
|
3
3
|
"description": "5-HTP, scientifically called 5-Hydroxytryptophan, is the precursor of happiness neurotransmitter.",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.2-2",
|
|
5
5
|
"author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
|
|
6
6
|
"repository": "git://github.com/gaetanlegac/5htp-core.git",
|
|
7
7
|
"license": "MIT",
|
|
@@ -58,6 +58,8 @@
|
|
|
58
58
|
"nodemailer": "^6.6.3",
|
|
59
59
|
"path-to-regexp": "^6.2.0",
|
|
60
60
|
"picomatch": "^2.3.1",
|
|
61
|
+
"preact": "^10.5.15",
|
|
62
|
+
"preact-render-to-string": "^5.1.19",
|
|
61
63
|
"react-scrollbars-custom": "^4.0.27",
|
|
62
64
|
"react-slider": "^2.0.1",
|
|
63
65
|
"react-textarea-autosize": "^8.3.3",
|
|
@@ -66,7 +68,6 @@
|
|
|
66
68
|
"request": "^2.88.2",
|
|
67
69
|
"sharp": "^0.29.1",
|
|
68
70
|
"sql-formatter": "^4.0.2",
|
|
69
|
-
"ts-alias": "^0.0.3-1",
|
|
70
71
|
"tslog": "^3.2.2",
|
|
71
72
|
"uuid": "^8.3.2",
|
|
72
73
|
"uuid-by-string": "^3.0.4",
|
|
@@ -83,9 +84,5 @@
|
|
|
83
84
|
"@types/universal-analytics": "^0.4.5",
|
|
84
85
|
"@types/webpack-env": "^1.16.2",
|
|
85
86
|
"@types/ws": "^7.4.7"
|
|
86
|
-
}
|
|
87
|
-
"peerDependencies": {
|
|
88
|
-
"preact": "^10.5.15",
|
|
89
|
-
"preact-render-to-string": "^5.1.19"
|
|
90
|
-
}
|
|
87
|
+
}
|
|
91
88
|
}
|
|
@@ -1,3 +1,10 @@
|
|
|
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
|
+
|
|
1
8
|
.card,
|
|
2
9
|
.input.text,
|
|
3
10
|
.btn,
|
|
@@ -7,10 +14,7 @@ i.solid {
|
|
|
7
14
|
.build-theme-bg( #fff, #8E8E8E);
|
|
8
15
|
|
|
9
16
|
&:not(.bg) {
|
|
10
|
-
|
|
11
|
-
box-shadow: 0 3px 2px fade(#000, 10%);
|
|
12
|
-
border: solid 1px fade(#000, 10%);
|
|
13
|
-
border-radius: @radius;
|
|
17
|
+
.white-card();
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
.bg & {
|
|
@@ -19,7 +19,6 @@ import SocketClient from './socket';
|
|
|
19
19
|
/*----------------------------------
|
|
20
20
|
- TYPES
|
|
21
21
|
----------------------------------*/
|
|
22
|
-
import { User, GuestUser } from '@common/models';
|
|
23
22
|
|
|
24
23
|
import type { Layout } from '@common/router';
|
|
25
24
|
|
|
@@ -63,7 +62,7 @@ export const useState = <TData extends TObjetDonnees>(initial: TData): [
|
|
|
63
62
|
export class ClientContext {
|
|
64
63
|
|
|
65
64
|
public context = this; // To access to the full nstance within a destructuration
|
|
66
|
-
public user: User |
|
|
65
|
+
public user: User | null;
|
|
67
66
|
|
|
68
67
|
public id = Date.now();
|
|
69
68
|
public bridges: { [name: string]: Function } = {};
|
|
@@ -109,7 +108,7 @@ export class ClientContext {
|
|
|
109
108
|
) {
|
|
110
109
|
|
|
111
110
|
this.request = request;
|
|
112
|
-
this.user = this.request.user ||
|
|
111
|
+
this.user = this.request.user || null;
|
|
113
112
|
|
|
114
113
|
}
|
|
115
114
|
|
|
@@ -120,7 +119,7 @@ export class ClientContext {
|
|
|
120
119
|
// Actions on the native app
|
|
121
120
|
reloadDaemon: () => { },
|
|
122
121
|
reloadGui: () => {
|
|
123
|
-
this.
|
|
122
|
+
this.page?.go('/');
|
|
124
123
|
},
|
|
125
124
|
optimize: () => {},
|
|
126
125
|
|
package/src/client/index.tsx
CHANGED
|
@@ -14,7 +14,6 @@ import router from '@client/router';
|
|
|
14
14
|
import { location } from '@client/router/request/history';
|
|
15
15
|
import coreRoutes from '@client/pages/**/*.tsx';
|
|
16
16
|
import appRoutes from '@/client/pages/**/*.tsx';
|
|
17
|
-
import { GuestUser } from '@common/models';
|
|
18
17
|
|
|
19
18
|
import ClientResponse from './router/response';
|
|
20
19
|
import ClientRequest from './router/request';
|
|
@@ -99,7 +98,7 @@ try {
|
|
|
99
98
|
if (!route)
|
|
100
99
|
throw new Error(`Route ${ssrResponse.page.id} was not found in ssr routes list.`);
|
|
101
100
|
|
|
102
|
-
context.user = request.user = ssrResponse.user ||
|
|
101
|
+
context.user = request.user = ssrResponse.user || null;
|
|
103
102
|
|
|
104
103
|
request.data = ssrResponse.request.data;
|
|
105
104
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// Npm
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import type { ComponentChild } from 'preact';
|
|
8
|
+
import { history } from './request/history';
|
|
9
|
+
|
|
10
|
+
/*----------------------------------
|
|
11
|
+
- COMPONENT
|
|
12
|
+
----------------------------------*/
|
|
13
|
+
// Simple link
|
|
14
|
+
export const Link = ({ to, ...props }: {
|
|
15
|
+
to: string,
|
|
16
|
+
children?: ComponentChild,
|
|
17
|
+
class?: string,
|
|
18
|
+
className?: string
|
|
19
|
+
} & React.HTMLProps<HTMLAnchorElement>) => {
|
|
20
|
+
|
|
21
|
+
// External = open in new tab by default
|
|
22
|
+
if (to[0] !== '/' || to.startsWith('//'))
|
|
23
|
+
props.target = '_blank';
|
|
24
|
+
// Otherwise, propagate to the router
|
|
25
|
+
else
|
|
26
|
+
props.onClick = (e) => {
|
|
27
|
+
history?.push(to);
|
|
28
|
+
e.preventDefault();
|
|
29
|
+
return false
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<a {...props} href={to} />
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
}
|
|
@@ -4,119 +4,102 @@
|
|
|
4
4
|
|
|
5
5
|
// Npm
|
|
6
6
|
import React from 'react';
|
|
7
|
-
import type { ComponentChild } from 'preact';
|
|
8
|
-
import type { Location } from 'history';
|
|
9
7
|
|
|
10
8
|
// Core: libs
|
|
11
9
|
import ClientRequest from './request';
|
|
12
10
|
import ClientResponse from './response';
|
|
13
|
-
import { history } from './request/history';
|
|
14
11
|
|
|
15
12
|
// Core: types
|
|
16
|
-
import BaseRouter, {
|
|
13
|
+
import BaseRouter, { defaultOptions, } from '@common/router'
|
|
17
14
|
import { PageResponse } from '@common/router/response';
|
|
18
|
-
import {
|
|
19
|
-
import type { TSsrData, default as ServerResponse } from '@server/services/router/response';
|
|
15
|
+
import type { TSsrData } from '@server/services/router/response';
|
|
20
16
|
import type { ClientContext } from '@client/context';
|
|
21
17
|
|
|
22
18
|
// Type xports
|
|
23
19
|
export type { default as ClientResponse } from "./response";
|
|
20
|
+
export { Link } from './Link';
|
|
21
|
+
import type {
|
|
22
|
+
TClientRoute,
|
|
23
|
+
TUnresolvedRoute,
|
|
24
|
+
TSsrUnresolvedRoute,
|
|
25
|
+
TRoutesLoaders,
|
|
26
|
+
TRouteCallback,
|
|
27
|
+
TFetchedRoute,
|
|
28
|
+
TRegisterPageArgs
|
|
29
|
+
} from './route';
|
|
30
|
+
|
|
31
|
+
// Temporary
|
|
32
|
+
// TODO: Import these types directly from router/routes
|
|
33
|
+
export type {
|
|
34
|
+
TClientRoute,
|
|
35
|
+
TUnresolvedRoute,
|
|
36
|
+
TSsrUnresolvedRoute,
|
|
37
|
+
TRoutesLoaders,
|
|
38
|
+
TRouteCallback,
|
|
39
|
+
TFetchedRoute,
|
|
40
|
+
TRegisterPageArgs,
|
|
41
|
+
TFrontController
|
|
42
|
+
} from './route';
|
|
24
43
|
|
|
25
44
|
/*----------------------------------
|
|
26
|
-
-
|
|
45
|
+
- CONFIG
|
|
27
46
|
----------------------------------*/
|
|
28
47
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
chunk: string,
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export type TUnresolvedRoute = Pick<TClientRoute, 'type' | 'keys'> & {
|
|
35
|
-
regex: RegExp,
|
|
36
|
-
chunk: string,
|
|
37
|
-
load: TRouteLoader,
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
type TFetchedRoute = Pick<TClientRoute, 'path' | 'options' | 'controller' | 'renderer' | 'method'>
|
|
48
|
+
const debug = true;
|
|
49
|
+
const LogPrefix = '[router]'
|
|
41
50
|
|
|
42
51
|
/*----------------------------------
|
|
43
|
-
- TYPES
|
|
52
|
+
- TYPES
|
|
44
53
|
----------------------------------*/
|
|
45
54
|
|
|
46
|
-
type
|
|
47
|
-
|
|
48
|
-
export type TRoutesLoaders = {
|
|
49
|
-
[chunkId: string]: /* Preloaded via require() */TFetchedRoute | /* Loader via import() */TRouteLoader/* | undefined*/
|
|
50
|
-
}
|
|
55
|
+
export type THookCallback = (request: ClientRequest) => void;
|
|
51
56
|
|
|
52
|
-
type
|
|
53
|
-
|
|
54
|
-
export type TRegisterPageArgs<TControllerData extends TFetcherList = {}> = [
|
|
55
|
-
path: string,
|
|
56
|
-
options: Partial<TRouteOptions>,
|
|
57
|
-
controller: TFrontController<TControllerData> | null,
|
|
58
|
-
renderer: TFrontRenderer<TControllerData>
|
|
59
|
-
];
|
|
57
|
+
type THookName = 'locationChange'
|
|
60
58
|
|
|
61
59
|
/*----------------------------------
|
|
62
|
-
-
|
|
60
|
+
- ROUTER
|
|
63
61
|
----------------------------------*/
|
|
62
|
+
class Router extends BaseRouter {
|
|
64
63
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
64
|
+
/*----------------------------------
|
|
65
|
+
- HOOKS
|
|
66
|
+
----------------------------------*/
|
|
67
|
+
private hooks: {
|
|
68
|
+
[hookname in THookName]?: (THookCallback | null)[]
|
|
69
|
+
} = {}
|
|
71
70
|
|
|
72
|
-
|
|
73
|
-
type TypeWithGeneric<T> = TFetcher<T>
|
|
74
|
-
type extractGeneric<Type> = Type extends TypeWithGeneric<infer X> ? X : never
|
|
75
|
-
|
|
76
|
-
export type TFrontController<TControllerData extends TFetcherList = {}> =
|
|
77
|
-
(urlParams: TObjetDonnees, context: ClientContext) => TControllerData
|
|
78
|
-
|
|
79
|
-
export type TFrontRenderer<TControllerData extends TFetcherList = {}> = (
|
|
80
|
-
data: {
|
|
81
|
-
[Property in keyof TControllerData]: undefined | (extractGeneric<TControllerData[Property]> extends ((...args: any[]) => any)
|
|
82
|
-
? ThenArg<ReturnType< extractGeneric<TControllerData[Property]> >>
|
|
83
|
-
: extractGeneric<TControllerData[Property]>
|
|
84
|
-
)
|
|
85
|
-
},
|
|
86
|
-
context: ClientContext
|
|
87
|
-
) => ComponentChild
|
|
88
|
-
|
|
89
|
-
// Simple link
|
|
90
|
-
export const Link = ({ to, ...props }: {
|
|
91
|
-
to: string,
|
|
92
|
-
children?: ComponentChild,
|
|
93
|
-
class?: string,
|
|
94
|
-
className?: string
|
|
95
|
-
}) => {
|
|
96
|
-
|
|
97
|
-
// External = open in new tab by default
|
|
98
|
-
if (to[0] !== '/' || to.startsWith('//'))
|
|
99
|
-
props.target = '_blank';
|
|
100
|
-
// Otherwise, propagate to the router
|
|
101
|
-
else
|
|
102
|
-
props.onClick = (e) => {
|
|
103
|
-
history?.push(to);
|
|
104
|
-
e.preventDefault();
|
|
105
|
-
return false
|
|
106
|
-
}
|
|
71
|
+
public on( hookName: THookName, callback: THookCallback ) {
|
|
107
72
|
|
|
108
|
-
|
|
109
|
-
<a {...props} href={to} />
|
|
110
|
-
)
|
|
73
|
+
debug && console.info(LogPrefix, `Register hook ${hookName}`);
|
|
111
74
|
|
|
112
|
-
|
|
75
|
+
let cbIndex: number;
|
|
76
|
+
let callbacks = this.hooks[ hookName ];
|
|
77
|
+
if (!callbacks) {
|
|
78
|
+
cbIndex = 0;
|
|
79
|
+
callbacks = this.hooks[ hookName ] = [callback]
|
|
80
|
+
} else {
|
|
81
|
+
cbIndex = callbacks.length;
|
|
82
|
+
callbacks.push(callback);
|
|
83
|
+
}
|
|
113
84
|
|
|
114
|
-
|
|
85
|
+
// Listener remover
|
|
86
|
+
return () => {
|
|
87
|
+
debug && console.info(LogPrefix, `De-register hook ${hookName} (index ${cbIndex})`);
|
|
88
|
+
delete (callbacks as THookCallback[])[ cbIndex ];
|
|
89
|
+
}
|
|
115
90
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
91
|
+
}
|
|
92
|
+
private runHook( hookName: THookName, request: ClientRequest ) {
|
|
93
|
+
const callbacks = this.hooks[hookName];
|
|
94
|
+
if (callbacks)
|
|
95
|
+
for (const callback of callbacks)
|
|
96
|
+
// callback can be null since we use delete to unregister
|
|
97
|
+
callback && callback(request);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/*----------------------------------
|
|
101
|
+
- ROUTES MANAGEMENT
|
|
102
|
+
----------------------------------*/
|
|
120
103
|
|
|
121
104
|
public disableResolver = false;
|
|
122
105
|
|
|
@@ -171,6 +154,8 @@ class Router extends BaseRouter {
|
|
|
171
154
|
public async resolve( request: ClientRequest, context: ClientContext ): Promise<PageResponse | undefined | null> {
|
|
172
155
|
debug && console.log('Resolving request', request.path, Object.keys(request.data));
|
|
173
156
|
|
|
157
|
+
this.runHook('locationChange', request);
|
|
158
|
+
|
|
174
159
|
for (let iRoute = 0; iRoute < this.routes.length; iRoute++) {
|
|
175
160
|
|
|
176
161
|
let route = this.routes[iRoute];
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// Npm
|
|
6
|
+
import type { ComponentChild } from 'preact';
|
|
7
|
+
|
|
8
|
+
// Core
|
|
9
|
+
import type { ClientContext } from '@client/context';
|
|
10
|
+
import type { TBaseRoute, TRouteOptions, } from '@common/router'
|
|
11
|
+
import type { TFetcher, TFetcherList } from '@common/router/request';
|
|
12
|
+
|
|
13
|
+
/*----------------------------------
|
|
14
|
+
- TYPES: PARTIAL ROUTES
|
|
15
|
+
----------------------------------*/
|
|
16
|
+
|
|
17
|
+
export type TSsrUnresolvedRoute = Pick<TClientRoute, 'type' | 'keys'> & {
|
|
18
|
+
regex: string,
|
|
19
|
+
chunk: string,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type TUnresolvedRoute = Pick<TClientRoute, 'type' | 'keys'> & {
|
|
23
|
+
regex: RegExp | null,
|
|
24
|
+
chunk: string,
|
|
25
|
+
load: TRouteLoader,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type TFetchedRoute = Pick<TClientRoute, 'path' | 'options' | 'controller' | 'renderer' | 'method'>
|
|
29
|
+
|
|
30
|
+
/*----------------------------------
|
|
31
|
+
- TYPES: REGISTER
|
|
32
|
+
----------------------------------*/
|
|
33
|
+
|
|
34
|
+
type TRouteLoader = () => Promise<{ default: TFetchedRoute }>;
|
|
35
|
+
|
|
36
|
+
export type TRoutesLoaders = {
|
|
37
|
+
[chunkId: string]: /* Preloaded via require() */TFetchedRoute | /* Loader via import() */TRouteLoader/* | undefined*/
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type TRouteCallback = (route?: TClientRoute) => void;
|
|
41
|
+
|
|
42
|
+
export type TRegisterPageArgs<TControllerData extends TFetcherList = {}> = [
|
|
43
|
+
path: string,
|
|
44
|
+
options: Partial<TRouteOptions>,
|
|
45
|
+
controller: TFrontController<TControllerData> | null,
|
|
46
|
+
renderer: TFrontRenderer<TControllerData>
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
/*----------------------------------
|
|
50
|
+
- TYPES: COMPLETE ROUTES
|
|
51
|
+
----------------------------------*/
|
|
52
|
+
|
|
53
|
+
export type TClientRoute = TBaseRoute & {
|
|
54
|
+
type: 'PAGE',
|
|
55
|
+
method: 'GET',
|
|
56
|
+
controller: TFrontController | null,
|
|
57
|
+
renderer: TFrontRenderer
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// https://stackoverflow.com/questions/44851268/typescript-how-to-extract-the-generic-parameter-from-a-type
|
|
61
|
+
type TypeWithGeneric<T> = TFetcher<T>
|
|
62
|
+
type extractGeneric<Type> = Type extends TypeWithGeneric<infer X> ? X : never
|
|
63
|
+
|
|
64
|
+
export type TFrontController<TControllerData extends TFetcherList = {}> =
|
|
65
|
+
(urlParams: TObjetDonnees, context: ClientContext) => TControllerData
|
|
66
|
+
|
|
67
|
+
export type TFrontRenderer<TControllerData extends TFetcherList = {}> = (
|
|
68
|
+
data: {
|
|
69
|
+
[Property in keyof TControllerData]: undefined | (extractGeneric<TControllerData[Property]> extends ((...args: any[]) => any)
|
|
70
|
+
? ThenArg<ReturnType< extractGeneric<TControllerData[Property]> >>
|
|
71
|
+
: extractGeneric<TControllerData[Property]>
|
|
72
|
+
)
|
|
73
|
+
},
|
|
74
|
+
context: ClientContext
|
|
75
|
+
) => ComponentChild
|
|
@@ -13,11 +13,13 @@ import type {
|
|
|
13
13
|
TFrontRenderer
|
|
14
14
|
} from '@client/router';
|
|
15
15
|
|
|
16
|
-
import type {
|
|
16
|
+
import type { ClientContext } from '@client/context';
|
|
17
17
|
|
|
18
18
|
import type { TSchema } from '@common/data/input/validate';
|
|
19
19
|
|
|
20
|
-
import {
|
|
20
|
+
import type { TApiServerRoute } from '@server/services/router';
|
|
21
|
+
|
|
22
|
+
import type { TUserRole } from '@server/services/auth/base';
|
|
21
23
|
|
|
22
24
|
/*----------------------------------
|
|
23
25
|
- TYPES: layouts
|
|
@@ -25,7 +27,7 @@ import { TUserRole } from '@common/models';
|
|
|
25
27
|
|
|
26
28
|
import layouts from '@/client/pages/**/_layout/index.tsx';
|
|
27
29
|
|
|
28
|
-
type LayoutComponent = ({ context: ClientContext }) => ComponentChild;
|
|
30
|
+
type LayoutComponent = (attributes: { context: ClientContext }) => ComponentChild;
|
|
29
31
|
export type Layout = { path: string, Component: LayoutComponent }
|
|
30
32
|
const getLayout = (routePath: string | undefined): Layout | undefined => {
|
|
31
33
|
|
|
@@ -84,7 +86,7 @@ export type TRouteOptions = {
|
|
|
84
86
|
|
|
85
87
|
}
|
|
86
88
|
|
|
87
|
-
export const defaultOptions
|
|
89
|
+
export const defaultOptions = {
|
|
88
90
|
priority: 0,
|
|
89
91
|
}
|
|
90
92
|
|
package/src/server/app/config.ts
CHANGED
|
@@ -30,14 +30,8 @@ declare global {
|
|
|
30
30
|
|
|
31
31
|
export type TEnvName = TEnvConfig["name"];
|
|
32
32
|
export type TEnvConfig = {
|
|
33
|
-
|
|
34
33
|
name: 'local' | 'server',
|
|
35
34
|
profile: 'dev' | 'prod',
|
|
36
|
-
level: 'silly' | 'info' | 'warn' | 'error',
|
|
37
|
-
|
|
38
|
-
localIP: string,
|
|
39
|
-
domain: string,
|
|
40
|
-
url: string,
|
|
41
35
|
}
|
|
42
36
|
|
|
43
37
|
type AppIdentityConfig = {
|
|
@@ -93,20 +87,10 @@ export default class ConfigParser {
|
|
|
93
87
|
console.log("Using environment:", process.env.NODE_ENV);
|
|
94
88
|
return process.env.NODE_ENV === 'development' ? {
|
|
95
89
|
name: 'local',
|
|
96
|
-
profile: 'dev'
|
|
97
|
-
level: 'silly',
|
|
98
|
-
|
|
99
|
-
localIP: '86.76.176.80',
|
|
100
|
-
domain: 'localhost:3010',
|
|
101
|
-
url: 'http://localhost:3010',
|
|
90
|
+
profile: 'dev'
|
|
102
91
|
} : {
|
|
103
92
|
name: 'server',
|
|
104
93
|
profile: 'prod',
|
|
105
|
-
level: 'silly',
|
|
106
|
-
|
|
107
|
-
localIP: '86.76.176.80',
|
|
108
|
-
domain: 'megacharger.io',
|
|
109
|
-
url: 'https://megacharger.io',
|
|
110
94
|
}
|
|
111
95
|
}
|
|
112
96
|
|
package/src/server/app/index.ts
CHANGED
|
@@ -74,7 +74,7 @@ export class App {
|
|
|
74
74
|
get: (container, serviceId, receiver) => {
|
|
75
75
|
|
|
76
76
|
if (!( serviceId in container ) && typeof serviceId === 'string')
|
|
77
|
-
throw new Error(`
|
|
77
|
+
throw new Error(`The following service is required as a dependancy: ${serviceId}`);
|
|
78
78
|
|
|
79
79
|
return container[serviceId];
|
|
80
80
|
}
|
|
@@ -34,7 +34,7 @@ export default ({ page, children: html, request, ssrData }: {
|
|
|
34
34
|
|
|
35
35
|
const routesForClient = JSON.stringify( services.router.ssrRoutes );
|
|
36
36
|
|
|
37
|
-
const fullUrl = services.http.
|
|
37
|
+
const fullUrl = services.http.publicUrl + request.path;
|
|
38
38
|
|
|
39
39
|
let attrsBody = {
|
|
40
40
|
className: [...page.bodyClass].join(' '),
|
|
@@ -10,7 +10,7 @@ import { OAuth2Client, LoginTicket } from 'google-auth-library';
|
|
|
10
10
|
import { Forbidden } from '@common/errors';
|
|
11
11
|
|
|
12
12
|
// App Libs
|
|
13
|
-
import { IP } from '@models';
|
|
13
|
+
import { IP } from '@server/models';
|
|
14
14
|
import app, { $ } from '@server/app';
|
|
15
15
|
|
|
16
16
|
// Serbices
|
|
@@ -22,6 +22,8 @@ import '@server/services/database';
|
|
|
22
22
|
|
|
23
23
|
import type ServerRequest from '@server/services/router/request';
|
|
24
24
|
|
|
25
|
+
export type TUserRole = typeof UserRoles[number]
|
|
26
|
+
|
|
25
27
|
type AuthResponse = {
|
|
26
28
|
token: string,
|
|
27
29
|
redirect: string,
|
|
@@ -36,6 +38,12 @@ const config = app.config.auth;
|
|
|
36
38
|
|
|
37
39
|
const LogPrefix = '[auth]'
|
|
38
40
|
|
|
41
|
+
export const UserRoles = ['USER', 'ADMIN', 'TEST', 'DEV'] as const
|
|
42
|
+
|
|
43
|
+
/*----------------------------------
|
|
44
|
+
- SERVICE CONVIG
|
|
45
|
+
----------------------------------*/
|
|
46
|
+
|
|
39
47
|
export type AuthConfig = {
|
|
40
48
|
debug: boolean,
|
|
41
49
|
logoutUrl: string,
|
|
@@ -81,16 +89,22 @@ export default abstract class UserAuthBase {
|
|
|
81
89
|
public abstract beforeSignup(user: User): Promise< void >;
|
|
82
90
|
public abstract afterSignup(user: User): Promise<{ redirect: string }>;
|
|
83
91
|
|
|
84
|
-
private googleClient
|
|
85
|
-
? new OAuth2Client(
|
|
86
|
-
app.config.auth.google.web.clientId, // Google Client ID
|
|
87
|
-
app.config.auth.google.web.secret, // Private key
|
|
88
|
-
"https://" + app.env.domain + "/auth/google/response" // Redirect url
|
|
89
|
-
)
|
|
90
|
-
: undefined;
|
|
92
|
+
private googleClient: OAuth2Client | undefined;
|
|
91
93
|
|
|
92
94
|
public async load() {
|
|
93
95
|
|
|
96
|
+
// Google auth client
|
|
97
|
+
if (app.config.auth.google) {
|
|
98
|
+
|
|
99
|
+
const httpConfig = app.services.http.publicUrl;
|
|
100
|
+
|
|
101
|
+
this.googleClient = new OAuth2Client(
|
|
102
|
+
app.config.auth.google.web.clientId, // Google Client ID
|
|
103
|
+
app.config.auth.google.web.secret, // Private key
|
|
104
|
+
httpConfig + "/auth/google/response" // Redirect url
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
94
108
|
}
|
|
95
109
|
|
|
96
110
|
public async FromEmail(
|
|
@@ -137,7 +151,8 @@ export default abstract class UserAuthBase {
|
|
|
137
151
|
request: ServerRequest,
|
|
138
152
|
): Promise<AuthResponse> {
|
|
139
153
|
|
|
140
|
-
|
|
154
|
+
const googleConfig = app.config.auth.google;
|
|
155
|
+
if (!this.googleClient || !googleConfig)
|
|
141
156
|
throw new Forbidden(`Authentication method disabled.`);
|
|
142
157
|
|
|
143
158
|
if (codeOrToken === undefined)
|
|
@@ -148,15 +163,15 @@ export default abstract class UserAuthBase {
|
|
|
148
163
|
return this.GoogleResponse('token', r.tokens.id_token, request);
|
|
149
164
|
}
|
|
150
165
|
|
|
151
|
-
config.debug && console.log(LogPrefix, "Auth via google",
|
|
166
|
+
config.debug && console.log(LogPrefix, "Auth via google", googleConfig);
|
|
152
167
|
|
|
153
168
|
let ticket: LoginTicket;
|
|
154
169
|
try {
|
|
155
170
|
ticket = await this.googleClient.verifyIdToken({
|
|
156
171
|
idToken: codeOrToken,
|
|
157
172
|
audience: [
|
|
158
|
-
|
|
159
|
-
|
|
173
|
+
googleConfig.web.clientId,
|
|
174
|
+
googleConfig.android.clientId,
|
|
160
175
|
]
|
|
161
176
|
});
|
|
162
177
|
} catch (error) {
|
|
@@ -32,6 +32,9 @@ type AppBugInfos = {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
export type ServerBug = {
|
|
35
|
+
|
|
36
|
+
type: 'server',
|
|
37
|
+
|
|
35
38
|
// Context
|
|
36
39
|
hash: string,
|
|
37
40
|
date: Date, // Timestamp
|
|
@@ -42,11 +45,15 @@ export type ServerBug = {
|
|
|
42
45
|
ip: string | null | undefined,
|
|
43
46
|
|
|
44
47
|
// Error
|
|
48
|
+
error: Error,
|
|
45
49
|
stacktrace: string,
|
|
46
|
-
logs: string
|
|
50
|
+
logs: string,
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
export type ApplicationBug = {
|
|
54
|
+
|
|
55
|
+
type: 'application',
|
|
56
|
+
|
|
50
57
|
// Context
|
|
51
58
|
hash: string,
|
|
52
59
|
date: Date,
|
|
@@ -146,6 +153,9 @@ export default class BugReporter {
|
|
|
146
153
|
);
|
|
147
154
|
|
|
148
155
|
const bugReport: ServerBug = {
|
|
156
|
+
|
|
157
|
+
type: 'server',
|
|
158
|
+
|
|
149
159
|
// Context
|
|
150
160
|
hash: hash,
|
|
151
161
|
date: now,
|
|
@@ -155,6 +165,7 @@ export default class BugReporter {
|
|
|
155
165
|
user: request?.user?.name,
|
|
156
166
|
ip: request?.ip,
|
|
157
167
|
// Error
|
|
168
|
+
error,
|
|
158
169
|
stacktrace: error.stack || error.message,
|
|
159
170
|
logs: logsHtml
|
|
160
171
|
}
|
|
@@ -193,6 +204,8 @@ export default class BugReporter {
|
|
|
193
204
|
|
|
194
205
|
const bugReport: ApplicationBug = {
|
|
195
206
|
|
|
207
|
+
type: 'application',
|
|
208
|
+
|
|
196
209
|
// Context
|
|
197
210
|
hash: hash,
|
|
198
211
|
date: now,
|
|
@@ -19,8 +19,15 @@ import BugReporter from "./bugReporter";
|
|
|
19
19
|
|
|
20
20
|
export type TReportTransport = keyof typeof $
|
|
21
21
|
|
|
22
|
+
type TLogProfile = 'silly' | 'info' | 'warn' | 'error'
|
|
23
|
+
|
|
22
24
|
export type ConsoleConfig = {
|
|
23
|
-
|
|
25
|
+
dev: {
|
|
26
|
+
level: TLogProfile,
|
|
27
|
+
},
|
|
28
|
+
prod: {
|
|
29
|
+
level: TLogProfile
|
|
30
|
+
}
|
|
24
31
|
}
|
|
25
32
|
|
|
26
33
|
declare global {
|
|
@@ -120,6 +127,8 @@ export class Console {
|
|
|
120
127
|
----------------------------------*/
|
|
121
128
|
public load() {
|
|
122
129
|
|
|
130
|
+
const envConfig = app.config.console[ app.env.profile ];
|
|
131
|
+
|
|
123
132
|
this.logger = new Logger({
|
|
124
133
|
overwriteConsole: true,
|
|
125
134
|
//type: app.env.profile === 'dev' ? 'pretty' : 'hidden',
|
|
@@ -138,7 +147,7 @@ export class Console {
|
|
|
138
147
|
warn: this.log.bind(this),
|
|
139
148
|
error: this.log.bind(this),
|
|
140
149
|
fatal: this.log.bind(this),
|
|
141
|
-
},
|
|
150
|
+
}, envConfig.level);
|
|
142
151
|
|
|
143
152
|
setInterval(() => this.clean(), 60000);
|
|
144
153
|
|
|
@@ -11,7 +11,6 @@ import app, { $ } from '@server/app';
|
|
|
11
11
|
//import templates from './templates';
|
|
12
12
|
const templates = {} as {[template: string]: (data: any) => string}
|
|
13
13
|
import { jsonToHtml } from './utils';
|
|
14
|
-
import greetings from '@common/data/chaines/greetings';
|
|
15
14
|
|
|
16
15
|
/*----------------------------------
|
|
17
16
|
- SERVICE CONFIG
|
|
@@ -20,10 +19,14 @@ import greetings from '@common/data/chaines/greetings';
|
|
|
20
19
|
export type EmailServiceConfig = {
|
|
21
20
|
debug: boolean,
|
|
22
21
|
default: {
|
|
23
|
-
transporter:
|
|
22
|
+
transporter: string,
|
|
24
23
|
from: string
|
|
25
24
|
},
|
|
26
|
-
transporters: Core.
|
|
25
|
+
transporters: Core.EmailTransporters,
|
|
26
|
+
bugReport: {
|
|
27
|
+
from: string,
|
|
28
|
+
to: string
|
|
29
|
+
}
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
declare global {
|
|
@@ -67,32 +70,15 @@ export type TCompleteEmail = With<THtmlEmail, {
|
|
|
67
70
|
- TYPES: OPTIONS
|
|
68
71
|
----------------------------------*/
|
|
69
72
|
|
|
70
|
-
type TransporterName = keyof Core.Config.EmailTransporters;
|
|
71
|
-
|
|
72
73
|
export abstract class Transporter {
|
|
73
74
|
public abstract send( emails: TCompleteEmail[] ): Promise<void>;
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
type TOptions = {
|
|
77
|
-
transporter?:
|
|
78
|
+
transporter?: string,
|
|
78
79
|
testing?: boolean
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
// TODO: to normalize
|
|
82
|
-
export const userMail = (username: string, content: string) => `
|
|
83
|
-
${greetings(username)}<br/><br/>
|
|
84
|
-
|
|
85
|
-
${content}
|
|
86
|
-
|
|
87
|
-
<br/><br/>
|
|
88
|
-
|
|
89
|
-
If you need any help, send me an email at <a href="mailto:contact@gaetan-legac.fr">contact@gaetan-legac.fr</a>, and I will reply to as soon as I can.
|
|
90
|
-
|
|
91
|
-
<br/><br/>
|
|
92
|
-
Peace,<br/>
|
|
93
|
-
<a href="https://www.linkedin.com/in/ga%C3%ABtanlegac/">Gaëtan Le Gac</a>
|
|
94
|
-
`
|
|
95
|
-
|
|
96
82
|
const config = app.config.email;
|
|
97
83
|
|
|
98
84
|
/*----------------------------------
|
|
@@ -100,23 +86,31 @@ const config = app.config.email;
|
|
|
100
86
|
----------------------------------*/
|
|
101
87
|
export default class Email {
|
|
102
88
|
|
|
103
|
-
private transporters = {} as {[name
|
|
104
|
-
public register( name:
|
|
89
|
+
private transporters = {} as {[name: string]: Transporter};
|
|
90
|
+
public register( name: string, transporter: (new () => Transporter) ) {
|
|
105
91
|
console.log(`[email] registering email transporter: ${name}`);
|
|
106
92
|
this.transporters[ name ] = new transporter();
|
|
107
93
|
}
|
|
108
94
|
|
|
109
95
|
public load() {
|
|
110
|
-
$.console.bugReport.addTransporter('email', (report
|
|
111
|
-
|
|
112
|
-
|
|
96
|
+
$.console.bugReport.addTransporter('email', (report) => this.send(report.type === 'server' ? {
|
|
97
|
+
from: config.bugReport.from,
|
|
98
|
+
to: config.bugReport.to,
|
|
99
|
+
subject: "Bug on server: " + (report.error.message),
|
|
113
100
|
html: `
|
|
114
|
-
<a href="${app.
|
|
101
|
+
<a href="${app.services.http.publicUrl}/admin/activity/requests/${report.channelId}">
|
|
115
102
|
View Request details & console
|
|
116
103
|
</a>
|
|
117
104
|
<br/>
|
|
118
105
|
${report.logs}
|
|
119
106
|
`
|
|
107
|
+
} : {
|
|
108
|
+
from: config.bugReport.from,
|
|
109
|
+
to: config.bugReport.to,
|
|
110
|
+
subject: "Bug on application " + (report.action),
|
|
111
|
+
html: {
|
|
112
|
+
...report
|
|
113
|
+
}
|
|
120
114
|
}));
|
|
121
115
|
|
|
122
116
|
}
|
|
@@ -11,9 +11,6 @@ import path from 'path';
|
|
|
11
11
|
import cors from 'cors';
|
|
12
12
|
//var serveStatic = require('serve-static')
|
|
13
13
|
|
|
14
|
-
// Npm: Autres
|
|
15
|
-
import fs from 'fs-extra';
|
|
16
|
-
|
|
17
14
|
// Middlewares (npm)
|
|
18
15
|
import morgan from 'morgan';
|
|
19
16
|
import hpp from 'hpp'; // Protection contre la pollution des reuqtees http
|
|
@@ -38,13 +35,18 @@ import { MiddlewareFormData } from './multipart';
|
|
|
38
35
|
----------------------------------*/
|
|
39
36
|
|
|
40
37
|
export type HttpServiceConfig = {
|
|
41
|
-
port: number
|
|
42
|
-
ssl: boolean,
|
|
43
38
|
|
|
39
|
+
// Access
|
|
40
|
+
domain: string,
|
|
41
|
+
port: number,
|
|
42
|
+
ssl: number,
|
|
43
|
+
|
|
44
|
+
// Limitations / Load restriction
|
|
44
45
|
upload: {
|
|
45
46
|
maxSize: string // Expression package bytes
|
|
46
47
|
},
|
|
47
48
|
|
|
49
|
+
// Protections against bots
|
|
48
50
|
security: {
|
|
49
51
|
recaptcha: {
|
|
50
52
|
prv: string,
|
|
@@ -75,35 +77,19 @@ export default class HttpServer {
|
|
|
75
77
|
public router: Router;
|
|
76
78
|
|
|
77
79
|
public config: HttpServiceConfig;
|
|
78
|
-
public
|
|
80
|
+
public publicUrl: string;
|
|
79
81
|
|
|
80
82
|
public constructor() {
|
|
81
83
|
|
|
82
84
|
// Init
|
|
83
85
|
this.config = app.config.http;
|
|
84
|
-
this.
|
|
85
|
-
|
|
86
|
+
this.publicUrl = app.env.name === 'local'
|
|
87
|
+
? 'http://localhost:' + this.config.port
|
|
88
|
+
: ((this.config.ssl ? 'https' : 'http') + '://' + this.config.domain);
|
|
86
89
|
|
|
87
90
|
// Configure HTTP server
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
this.http = http.createServer(this.express);
|
|
91
|
-
|
|
92
|
-
} /*else if ('ssh' in app.env) {
|
|
93
|
-
|
|
94
|
-
const ssh = app.env.ssh;
|
|
95
|
-
|
|
96
|
-
console.log("Création du serveur https pour le socket:", '/home/' + ssh.login + '/ssl.*');
|
|
97
|
-
this.http = https.createServer({
|
|
98
|
-
key: fs.readFileSync('/home/' + ssh.login + '/ssl.key'),
|
|
99
|
-
cert: fs.readFileSync('/home/' + ssh.login + '/ssl.cert'),
|
|
100
|
-
ca: fs.readFileSync('/home/' + ssh.login + '/ssl.ca'),
|
|
101
|
-
requestCert: true,
|
|
102
|
-
rejectUnauthorized: false
|
|
103
|
-
}, this.express);
|
|
104
|
-
|
|
105
|
-
}*/ else
|
|
106
|
-
throw new Error(`SSL was enabled, but no ssh config was specified in app.env (required to load ssl certificate files)`);
|
|
91
|
+
this.express = express();
|
|
92
|
+
this.http = http.createServer(this.express);
|
|
107
93
|
|
|
108
94
|
// Start HTTP Server
|
|
109
95
|
this.router = app.services.router;
|
|
@@ -248,7 +234,7 @@ export default class HttpServer {
|
|
|
248
234
|
// Impossible donc de créer un serveur http ici, on le fera dans start.js
|
|
249
235
|
console.info("Lancement du serveur web");
|
|
250
236
|
this.http.listen(this.config.port, () => {
|
|
251
|
-
console.info(`Serveur web démarré sur
|
|
237
|
+
console.info(`Serveur web démarré sur ${this.publicUrl}`);
|
|
252
238
|
});
|
|
253
239
|
|
|
254
240
|
}
|
|
File without changes
|
|
@@ -10,14 +10,12 @@ import jwt from 'jsonwebtoken';
|
|
|
10
10
|
// Cre
|
|
11
11
|
import app, { $ } from '@server/app';
|
|
12
12
|
import { InputError, AuthRequired, Forbidden } from '@common/errors';
|
|
13
|
-
import { TUserRole } from '@
|
|
13
|
+
import type { TUserRole } from '@server/services/auth/base';
|
|
14
14
|
|
|
15
15
|
/*----------------------------------
|
|
16
16
|
- TYPES
|
|
17
17
|
----------------------------------*/
|
|
18
18
|
|
|
19
|
-
import { User } from '@models';
|
|
20
|
-
|
|
21
19
|
import type ServerRequest from '@server/services/router/request'
|
|
22
20
|
|
|
23
21
|
type TJwtSession = { email: string }
|
package/src/types/aliases.d.ts
CHANGED
|
@@ -7,17 +7,6 @@ declare module "@client/pages/\*.tsx" {
|
|
|
7
7
|
export = value;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
// Basic Models
|
|
11
|
-
declare module "@/server/models/User" {
|
|
12
|
-
const User: import("../common/models").User;
|
|
13
|
-
export = User;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
declare module "@/server/models/IP" {
|
|
17
|
-
const IP: import("../common/models").IP;
|
|
18
|
-
export = IP;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
10
|
declare module "@/server/services/auth" {
|
|
22
11
|
const UserAuthService: import("../server/services/auth/base").default;
|
|
23
12
|
export = UserAuthService;
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export const UserRoles = ['USER', 'ADMIN', 'TEST', 'DEV'] as const
|
|
4
|
-
export type TUserRole = typeof UserRoles[number]
|
|
5
|
-
|
|
6
|
-
export interface User {
|
|
7
|
-
name: string,
|
|
8
|
-
email: string,
|
|
9
|
-
emailHash: string,
|
|
10
|
-
roles: TUserRole[],
|
|
11
|
-
|
|
12
|
-
balance: number,
|
|
13
|
-
banned?: Date,
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export const GuestUser = {
|
|
17
|
-
name: "Guest",
|
|
18
|
-
|
|
19
|
-
balance: 10,
|
|
20
|
-
multiplier: 1,
|
|
21
|
-
level: 1,
|
|
22
|
-
|
|
23
|
-
isGuest: true
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface IP {
|
|
27
|
-
address: string,
|
|
28
|
-
|
|
29
|
-
country: string,
|
|
30
|
-
isp?: string,
|
|
31
|
-
user_name?: string,
|
|
32
|
-
|
|
33
|
-
meet: Date,
|
|
34
|
-
activity: Date,
|
|
35
|
-
updated?: Date,
|
|
36
|
-
|
|
37
|
-
iphub?: number,
|
|
38
|
-
getipintel?: number,
|
|
39
|
-
ipinfo?: number,
|
|
40
|
-
|
|
41
|
-
banned?: Date,
|
|
42
|
-
banReason?: string,
|
|
43
|
-
}
|