5htp-core 0.0.6 → 0.0.8
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 +6 -4
- package/src/client/assets/css/components/button.less +1 -1
- package/src/client/context/index.ts +3 -2
- package/src/client/index.tsx +2 -1
- package/src/client/router/component.tsx +6 -6
- package/src/server/app/config.ts +23 -19
- package/src/server/data/Cache.ts +124 -74
- package/src/server/services/database/connection.ts +7 -5
- package/src/server/services/database/index.ts +12 -4
- package/src/server/services/http/index.ts +2 -2
- package/tsconfig.common.json +3 -3
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.0.
|
|
4
|
+
"version": "0.0.8",
|
|
5
5
|
"author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
|
|
6
6
|
"repository": "git://github.com/gaetanlegac/5htp-core.git",
|
|
7
7
|
"license": "MIT",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"fast-safe-stringify": "^2.1.1",
|
|
38
38
|
"fs-extra": "^10.1.0",
|
|
39
39
|
"google-auth-library": "^7.11.0",
|
|
40
|
+
"got": "^11.8.3",
|
|
40
41
|
"handlebars": "^4.7.7",
|
|
41
42
|
"helmet": "^4.6.0",
|
|
42
43
|
"history": "^5.0.1",
|
|
@@ -57,7 +58,6 @@
|
|
|
57
58
|
"nodemailer": "^6.6.3",
|
|
58
59
|
"path-to-regexp": "^6.2.0",
|
|
59
60
|
"picomatch": "^2.3.1",
|
|
60
|
-
"preact-render-to-string": "^5.1.19",
|
|
61
61
|
"react-scrollbars-custom": "^4.0.27",
|
|
62
62
|
"react-slider": "^2.0.1",
|
|
63
63
|
"react-textarea-autosize": "^8.3.3",
|
|
@@ -72,8 +72,10 @@
|
|
|
72
72
|
"uuid-by-string": "^3.0.4",
|
|
73
73
|
"validator": "^13.7.0",
|
|
74
74
|
"ws": "^8.2.2",
|
|
75
|
-
"yaml": "^1.10.2"
|
|
76
|
-
|
|
75
|
+
"yaml": "^1.10.2",
|
|
76
|
+
"preact": "^10.5.15",
|
|
77
|
+
"preact-render-to-string": "^5.1.19"
|
|
78
|
+
},
|
|
77
79
|
"devDependencies": {
|
|
78
80
|
"@types/cookie": "^0.4.1",
|
|
79
81
|
"@types/express": "^4.17.13",
|
|
@@ -202,7 +202,7 @@ export class ClientContext {
|
|
|
202
202
|
public handleError(e: Error) {
|
|
203
203
|
switch (e.http) {
|
|
204
204
|
case 401:
|
|
205
|
-
this.page?.
|
|
205
|
+
this.page?.go('/');
|
|
206
206
|
break;
|
|
207
207
|
default:
|
|
208
208
|
this.toast.error(e.title || "Uh Oh ...", e.message, null, { autohide: false });
|
|
@@ -225,12 +225,13 @@ export class ClientContext {
|
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
// Services
|
|
228
229
|
public modal = createDialog(this, false);
|
|
229
230
|
public toast = createDialog(this, true);
|
|
230
|
-
|
|
231
231
|
public socket = new SocketClient(this)
|
|
232
232
|
public captcha = new Recaptcha(this);
|
|
233
233
|
|
|
234
|
+
// Tracking
|
|
234
235
|
public event( name: string, params?: object ) {
|
|
235
236
|
if (!window.gtag) return;
|
|
236
237
|
if (name === 'pageview')
|
package/src/client/index.tsx
CHANGED
|
@@ -69,10 +69,10 @@ export default () => {
|
|
|
69
69
|
|
|
70
70
|
const [pages, setPages] = React.useState<{
|
|
71
71
|
current: undefined | PageResponse,
|
|
72
|
-
previous: undefined | PageResponse
|
|
72
|
+
//previous: undefined | PageResponse
|
|
73
73
|
}>({
|
|
74
74
|
current: gui.page,
|
|
75
|
-
previous: undefined
|
|
75
|
+
//previous: undefined
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
const resolvePage = async (request: ClientRequest, locationUpdate?: Update) => {
|
|
@@ -116,13 +116,13 @@ export default () => {
|
|
|
116
116
|
if (oldPage !== undefined) {
|
|
117
117
|
setTimeout(() => setPages({
|
|
118
118
|
current: newpage,
|
|
119
|
-
previous: undefined
|
|
119
|
+
//previous: undefined
|
|
120
120
|
}), 500);
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
return {
|
|
124
124
|
current: newpage,
|
|
125
|
-
previous: oldPage
|
|
125
|
+
//previous: oldPage
|
|
126
126
|
}
|
|
127
127
|
});
|
|
128
128
|
}
|
|
@@ -175,9 +175,9 @@ export default () => {
|
|
|
175
175
|
|
|
176
176
|
// Render the page component
|
|
177
177
|
return <>
|
|
178
|
-
{pages.previous && (
|
|
178
|
+
{/*pages.previous && (
|
|
179
179
|
<Page page={pages.previous} key={pages.previous.id === undefined ? undefined : 'page_' + pages.previous.id} />
|
|
180
|
-
)}
|
|
180
|
+
)*/}
|
|
181
181
|
|
|
182
182
|
{pages.current && (
|
|
183
183
|
<Page page={pages.current} isCurrent key={pages.current.id === undefined ? undefined : 'page_' + pages.current.id} />
|
package/src/server/app/config.ts
CHANGED
|
@@ -35,23 +35,9 @@ export type TEnvConfig = {
|
|
|
35
35
|
profile: 'dev' | 'prod',
|
|
36
36
|
level: 'silly' | 'info' | 'warn' | 'error',
|
|
37
37
|
|
|
38
|
+
localIP: string,
|
|
38
39
|
domain: string,
|
|
39
|
-
|
|
40
|
-
url: string, // protocol + domain
|
|
41
|
-
localIP?: string
|
|
42
|
-
|
|
43
|
-
http: {
|
|
44
|
-
port: number,
|
|
45
|
-
ssl: boolean
|
|
46
|
-
},
|
|
47
|
-
|
|
48
|
-
database: {
|
|
49
|
-
host: string,
|
|
50
|
-
port: number,
|
|
51
|
-
login: string,
|
|
52
|
-
password: string,
|
|
53
|
-
list: string[]
|
|
54
|
-
},
|
|
40
|
+
url: string,
|
|
55
41
|
}
|
|
56
42
|
|
|
57
43
|
type AppIdentityConfig = {
|
|
@@ -101,9 +87,27 @@ export default class ConfigParser {
|
|
|
101
87
|
return yaml.parse(rawConfig);
|
|
102
88
|
}
|
|
103
89
|
|
|
104
|
-
public env() {
|
|
105
|
-
|
|
106
|
-
|
|
90
|
+
public env(): TEnvConfig {
|
|
91
|
+
// We assume that when we run 5htp dev, we're in local
|
|
92
|
+
// Otherwise, we're in production environment (docker)
|
|
93
|
+
console.log("Using environment:", process.env.NODE_ENV);
|
|
94
|
+
return process.env.NODE_ENV === 'development' ? {
|
|
95
|
+
name: 'local',
|
|
96
|
+
profile: 'dev',
|
|
97
|
+
level: 'silly',
|
|
98
|
+
|
|
99
|
+
localIP: '86.76.176.80',
|
|
100
|
+
domain: 'localhost:3010',
|
|
101
|
+
url: 'http://localhost:3010',
|
|
102
|
+
} : {
|
|
103
|
+
name: 'server',
|
|
104
|
+
profile: 'prod',
|
|
105
|
+
level: 'silly',
|
|
106
|
+
|
|
107
|
+
localIP: '86.76.176.80',
|
|
108
|
+
domain: 'megacharger.io',
|
|
109
|
+
url: 'https://megacharger.io',
|
|
110
|
+
}
|
|
107
111
|
}
|
|
108
112
|
|
|
109
113
|
public identity() {
|
package/src/server/data/Cache.ts
CHANGED
|
@@ -2,68 +2,123 @@
|
|
|
2
2
|
- DEPENDANCES
|
|
3
3
|
----------------------------------*/
|
|
4
4
|
|
|
5
|
+
// Node
|
|
6
|
+
import path from 'path';
|
|
7
|
+
|
|
5
8
|
// Npm
|
|
6
9
|
import hInterval from 'human-interval';
|
|
10
|
+
import fs from 'fs-extra';
|
|
11
|
+
|
|
12
|
+
// Core
|
|
13
|
+
import app from '@server/app';
|
|
7
14
|
|
|
8
15
|
// Libs
|
|
9
|
-
|
|
10
|
-
|
|
16
|
+
|
|
17
|
+
/*----------------------------------
|
|
18
|
+
- TYPES
|
|
19
|
+
----------------------------------*/
|
|
20
|
+
|
|
21
|
+
type TPrimitiveValue = string | boolean | number | undefined | {[key: string]: TPrimitiveValue} | TPrimitiveValue[];
|
|
22
|
+
|
|
23
|
+
type TExpirationDelay = string | number | Date | null;
|
|
24
|
+
|
|
25
|
+
type CacheEntry = {
|
|
26
|
+
// Value
|
|
27
|
+
value: TPrimitiveValue,
|
|
28
|
+
// Expiration Timestamp
|
|
29
|
+
expiration?: number
|
|
30
|
+
};
|
|
11
31
|
|
|
12
32
|
/*----------------------------------
|
|
13
33
|
- SERVICE
|
|
14
34
|
----------------------------------*/
|
|
15
|
-
|
|
35
|
+
class Cache {
|
|
36
|
+
|
|
37
|
+
private cacheFile = path.join(app.path.data, 'cache/mem.json');
|
|
38
|
+
|
|
39
|
+
private data: {[key: string]: CacheEntry | undefined} = {};
|
|
40
|
+
|
|
41
|
+
private changes: number = 0;
|
|
42
|
+
|
|
43
|
+
public constructor() {
|
|
44
|
+
|
|
45
|
+
setInterval(() => this.cleanMem(), 10000);
|
|
46
|
+
|
|
47
|
+
if (fs.existsSync(this.cacheFile))
|
|
48
|
+
this.data = fs.readJSONSync(this.cacheFile)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private cleanMem() {
|
|
52
|
+
|
|
53
|
+
console.log("[cache] Clean memory");
|
|
54
|
+
|
|
55
|
+
const now = Date.now();
|
|
56
|
+
for (const key in this.data) {
|
|
57
|
+
const entry = this.data[ key ];
|
|
58
|
+
if (entry?.expiration && entry.expiration < now)
|
|
59
|
+
this.del(key);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (this.changes > 0)
|
|
63
|
+
fs.outputJSONSync(this.cacheFile, this.data);
|
|
64
|
+
}
|
|
16
65
|
|
|
17
66
|
// Expiration = Durée de vie en secondes ou date max
|
|
18
67
|
// Retourne null quand pas de valeur
|
|
19
|
-
|
|
68
|
+
public get<TValeur extends TPrimitiveValue>(
|
|
69
|
+
cle: string,
|
|
70
|
+
func?: (() => Promise<TValeur>),
|
|
71
|
+
expiration?: TExpirationDelay,
|
|
72
|
+
avecDetails?: true
|
|
73
|
+
): Promise<CacheEntry>;
|
|
74
|
+
|
|
75
|
+
public get<TValeur extends TPrimitiveValue>(
|
|
76
|
+
cle: string,
|
|
77
|
+
func: (() => Promise<TValeur>),
|
|
78
|
+
expiration?: TExpirationDelay,
|
|
79
|
+
avecDetails?: false
|
|
80
|
+
): Promise<null | TValeur>;
|
|
81
|
+
|
|
82
|
+
public async get<TValeur extends TPrimitiveValue>(
|
|
20
83
|
cle: string,
|
|
21
|
-
func
|
|
22
|
-
expiration
|
|
84
|
+
func?: (() => Promise<TValeur>),
|
|
85
|
+
expiration?: TExpirationDelay,
|
|
23
86
|
avecDetails?: boolean
|
|
24
|
-
): Promise<null | TValeur> {
|
|
87
|
+
): Promise<null | TValeur | CacheEntry> {
|
|
88
|
+
|
|
89
|
+
let retour: CacheEntry | undefined = this.data[cle];
|
|
90
|
+
|
|
91
|
+
console.log(`[cache] Get "${cle}".`);
|
|
25
92
|
|
|
26
|
-
|
|
93
|
+
// Expired
|
|
94
|
+
if (retour?.expiration && retour.expiration < Date.now()){
|
|
95
|
+
console.log(`[cache] Key ${cle} expired.`);
|
|
96
|
+
retour = undefined;
|
|
97
|
+
}
|
|
27
98
|
|
|
28
99
|
// Donnée inexistante
|
|
29
|
-
if (retour ===
|
|
100
|
+
if (retour === undefined && func !== undefined) {
|
|
30
101
|
|
|
31
102
|
// Rechargement
|
|
32
|
-
retour =
|
|
103
|
+
retour = {
|
|
104
|
+
value: await func(),
|
|
105
|
+
expiration: expiration
|
|
106
|
+
? this.delayToTimestamp(expiration)
|
|
107
|
+
: undefined
|
|
108
|
+
}
|
|
33
109
|
|
|
34
110
|
// undefined retourné = pas d'enregistrement
|
|
35
|
-
if (retour !== undefined)
|
|
111
|
+
if (retour.value !== undefined)
|
|
36
112
|
await this.set(cle, retour, expiration);
|
|
37
113
|
}
|
|
38
114
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
donnees: retour
|
|
42
|
-
}
|
|
43
|
-
: retour;
|
|
44
|
-
},
|
|
45
|
-
|
|
46
|
-
getVal<TValeur>(cle: string): Promise<TValeur | null> {
|
|
47
|
-
return new Promise((resolve) => {
|
|
48
|
-
|
|
49
|
-
redis.instance.get(cle, (err, val) => {
|
|
115
|
+
if (retour === undefined)
|
|
116
|
+
return null;
|
|
50
117
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
resolve( JSON.parse(val) )
|
|
56
|
-
} catch (error) {
|
|
57
|
-
|
|
58
|
-
console.warn(`Error while parsing JSON value from cache (id: ${cle})`, error, 'Raw value:', val);
|
|
59
|
-
resolve(null);
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
},
|
|
118
|
+
return avecDetails
|
|
119
|
+
? retour
|
|
120
|
+
: retour.value as TValeur;
|
|
121
|
+
};
|
|
67
122
|
|
|
68
123
|
/**
|
|
69
124
|
* Put in cache a JSON value, associated with an unique ID.
|
|
@@ -76,49 +131,44 @@ const Cache = {
|
|
|
76
131
|
* - null: no expiration (default)
|
|
77
132
|
* @returns A void promise
|
|
78
133
|
*/
|
|
79
|
-
set( cle: string, val:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
134
|
+
public set( cle: string, val: TPrimitiveValue, expiration: TExpirationDelay = null ): void {
|
|
135
|
+
|
|
136
|
+
console.log("[cache] Updating cache " + cle);
|
|
137
|
+
this.data[ cle ] = {
|
|
138
|
+
value: val,
|
|
139
|
+
expiration: this.delayToTimestamp(expiration)
|
|
140
|
+
}
|
|
86
141
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
redis.instance.set(cle, val, () => {
|
|
90
|
-
resolve()
|
|
91
|
-
});
|
|
92
|
-
else {
|
|
142
|
+
this.changes++;
|
|
143
|
+
};
|
|
93
144
|
|
|
94
|
-
|
|
145
|
+
public del( cle: string ): void {
|
|
146
|
+
this.data[ cle ] = undefined;
|
|
147
|
+
this.changes++;
|
|
148
|
+
}
|
|
95
149
|
|
|
96
|
-
// H expression
|
|
97
|
-
if (typeof expiration === 'string') {
|
|
98
150
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
151
|
+
/*----------------------------------
|
|
152
|
+
- UTILS
|
|
153
|
+
----------------------------------*/
|
|
154
|
+
private delayToTimestamp( delay: TExpirationDelay ): number {
|
|
102
155
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
secondes = expiration;
|
|
106
|
-
// Date limite
|
|
107
|
-
else
|
|
108
|
-
secondes = (expiration.getTime() - (new Date).getTime()) / 1000;
|
|
156
|
+
// H expression
|
|
157
|
+
if (typeof delay === 'string') {
|
|
109
158
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
},
|
|
159
|
+
const ms = hInterval(delay);
|
|
160
|
+
if (ms === undefined) throw new Error(`Invalid period string: ` + delay);
|
|
161
|
+
return Date.now() + ms;
|
|
116
162
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
163
|
+
// Via durée de vie en secondes
|
|
164
|
+
} else if (typeof delay === 'number')
|
|
165
|
+
return Date.now() + delay;
|
|
166
|
+
// Date limite
|
|
167
|
+
else if (delay !== null)
|
|
168
|
+
return delay.getTime();
|
|
169
|
+
else
|
|
170
|
+
return Date.now();
|
|
121
171
|
}
|
|
122
172
|
}
|
|
123
173
|
|
|
124
|
-
export default Cache;
|
|
174
|
+
export default new Cache;
|
|
@@ -62,13 +62,15 @@ export default class FastDatabase {
|
|
|
62
62
|
|
|
63
63
|
console.info(`Connecting to databases ...`);
|
|
64
64
|
|
|
65
|
+
const creds = this.config[ app.env.profile ];
|
|
66
|
+
|
|
65
67
|
return await mysql.createPool({
|
|
66
68
|
|
|
67
69
|
// Identification
|
|
68
|
-
host:
|
|
69
|
-
port:
|
|
70
|
-
user:
|
|
71
|
-
password:
|
|
70
|
+
host: creds.host,
|
|
71
|
+
port: creds.port,
|
|
72
|
+
user: creds.login,
|
|
73
|
+
password: creds.password,
|
|
72
74
|
database: this.config.list[0],
|
|
73
75
|
|
|
74
76
|
// Pool
|
|
@@ -106,7 +108,7 @@ export default class FastDatabase {
|
|
|
106
108
|
let type = field.type;
|
|
107
109
|
if (field.db) {
|
|
108
110
|
|
|
109
|
-
// A revoir, car les infos passées peuvent être des alias
|
|
111
|
+
// TODO: A revoir, car les infos passées peuvent être des alias
|
|
110
112
|
|
|
111
113
|
const db = this.tables[ field.db ];
|
|
112
114
|
if (db === undefined) {
|
|
@@ -41,11 +41,19 @@ export type TInsertQueryOptions<TData extends TObjetDonnees = TObjetDonnees> = T
|
|
|
41
41
|
----------------------------------*/
|
|
42
42
|
|
|
43
43
|
export type DatabaseServiceConfig = {
|
|
44
|
-
host: string,
|
|
45
44
|
list: string[],
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
dev: {
|
|
46
|
+
host: string,
|
|
47
|
+
port: number,
|
|
48
|
+
login: string,
|
|
49
|
+
password: string,
|
|
50
|
+
},
|
|
51
|
+
prod: {
|
|
52
|
+
host: string,
|
|
53
|
+
port: number,
|
|
54
|
+
login: string,
|
|
55
|
+
password: string,
|
|
56
|
+
}
|
|
49
57
|
}
|
|
50
58
|
|
|
51
59
|
declare global {
|
|
@@ -89,7 +89,7 @@ export default class HttpServer {
|
|
|
89
89
|
|
|
90
90
|
this.http = http.createServer(this.express);
|
|
91
91
|
|
|
92
|
-
} else if ('ssh' in app.env) {
|
|
92
|
+
} /*else if ('ssh' in app.env) {
|
|
93
93
|
|
|
94
94
|
const ssh = app.env.ssh;
|
|
95
95
|
|
|
@@ -102,7 +102,7 @@ export default class HttpServer {
|
|
|
102
102
|
rejectUnauthorized: false
|
|
103
103
|
}, this.express);
|
|
104
104
|
|
|
105
|
-
} else
|
|
105
|
+
}*/ else
|
|
106
106
|
throw new Error(`SSL was enabled, but no ssh config was specified in app.env (required to load ssl certificate files)`);
|
|
107
107
|
|
|
108
108
|
// Start HTTP Server
|
package/tsconfig.common.json
CHANGED
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"@errors": ["./common/errors"],
|
|
35
35
|
"@models": ["./common/models"],
|
|
36
36
|
|
|
37
|
-
"react": ["
|
|
38
|
-
"react-dom": ["
|
|
39
|
-
"react/jsx-runtime": ["
|
|
37
|
+
"react": ["preact/compat"],
|
|
38
|
+
"react-dom": ["preact/compat"],
|
|
39
|
+
"react/jsx-runtime": ["preact/jsx-runtime"]
|
|
40
40
|
},
|
|
41
41
|
},
|
|
42
42
|
"include": ["src"]
|