5htp-core 0.2.7 → 0.2.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 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.7",
4
+ "version": "0.2.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",
@@ -46,6 +46,7 @@ export type TDialogControls = {
46
46
  type DialogActions = {
47
47
 
48
48
  setToasts: ( setter: (old: ComponentChild[]) => ComponentChild[]) => void,
49
+ setModals: ( setter: (old: ComponentChild[]) => ComponentChild[]) => void,
49
50
 
50
51
  show: (
51
52
  // On utilise une fonction pour pouvoir accéder aux fonctions (close, ...) lors de la déclaration des infos de la toast
@@ -81,9 +82,13 @@ export const createDialog = (app: Application, isToast: boolean): DialogActions
81
82
  let onClose: TOnCloseCallback<TReturnType>;
82
83
  const id = idA++;
83
84
 
85
+ const setDialog = isToast
86
+ ? instance.setToasts
87
+ : instance.setModals;
88
+
84
89
  const close = (retour: TReturnType) => {
85
90
 
86
- instance.setToasts(q => q.filter(m => m.id !== id))
91
+ setDialog(q => q.filter(m => m.id !== id))
87
92
 
88
93
  if (onClose !== undefined)
89
94
  onClose(retour);
@@ -145,7 +150,7 @@ export const createDialog = (app: Application, isToast: boolean): DialogActions
145
150
 
146
151
  render["id"] = id;
147
152
 
148
- instance.setToasts(q => [...q, render]);
153
+ setDialog(q => [...q, render]);
149
154
  });
150
155
 
151
156
  return {
@@ -159,6 +164,7 @@ export const createDialog = (app: Application, isToast: boolean): DialogActions
159
164
  show: show,
160
165
 
161
166
  setToasts: undefined as unknown as DialogActions["setToasts"],
167
+ setModals: undefined as unknown as DialogActions["setModals"],
162
168
 
163
169
  confirm: (title: string, content: string | ComponentChild, defaultBtn: 'Yes'|'No' = 'No') => show<boolean>(({ close }) => (
164
170
  <div class="card col">
@@ -228,16 +234,19 @@ export default () => {
228
234
 
229
235
  const app = useContext();
230
236
 
231
- const [rendered, setRendered] = React.useState<ComponentChild[]>([]);
237
+ const [modals, setModals] = React.useState<ComponentChild[]>([]);
238
+ const [toasts, setToasts] = React.useState<ComponentChild[]>([]);
232
239
 
233
- if (app.side === 'client')
234
- app.modal.setToasts = app.toast.setToasts = setRendered;
240
+ if (app.side === 'client') {
241
+ app.modal.setModals = setModals;
242
+ app.toast.setToasts = setToasts;
243
+ }
235
244
 
236
245
  React.useEffect(() => {
237
246
 
238
247
  console.log('Updated toast list');
239
248
 
240
- const modals = document.querySelectorAll("#dialog > .modal");
249
+ const modals = document.querySelectorAll("#modals > .modal");
241
250
  if (modals.length === 0)
242
251
  return;
243
252
 
@@ -259,10 +268,18 @@ export default () => {
259
268
 
260
269
  });
261
270
 
262
- return rendered.length !== 0 ? (
263
- <div id="dialog">
264
- {rendered}
265
- </div>
266
- ) : null;
271
+ return <>
272
+ {modals.length !== 0 ? (
273
+ <div id="modals">
274
+ {modals}
275
+ </div>
276
+ ) : null}
277
+
278
+ {toasts.length !== 0 ? (
279
+ <div id="toasts">
280
+ {toasts}
281
+ </div>
282
+ ) : null}
283
+ </>
267
284
 
268
285
  }
@@ -169,7 +169,6 @@ export default ({
169
169
  ) : title}
170
170
 
171
171
  {(!prison && close) && (
172
-
173
172
  <Button class="close" icon="solid/times" size="s" shape="pill" onClick={async () => {
174
173
  if (typeof close === "function") {
175
174
 
@@ -179,7 +178,6 @@ export default ({
179
178
  close(false);
180
179
  }
181
180
  }} />
182
-
183
181
  )}
184
182
 
185
183
  </header>
@@ -1,6 +1,6 @@
1
1
  @toast-zindex: 999;
2
2
 
3
- #dialog {
3
+ #modals, #toasts {
4
4
 
5
5
  z-index: @toast-zindex;
6
6
 
@@ -10,11 +10,10 @@
10
10
  justify-content: flex-end;
11
11
  gap: @spacing;
12
12
  padding: @spacing;
13
-
13
+
14
14
  &,
15
15
  > .modal {
16
16
  position: fixed;
17
- top: 0px;
18
17
  left: 0px;
19
18
  right: 0px;
20
19
  bottom: 0px;
@@ -35,40 +34,13 @@
35
34
  margin-top: 0;
36
35
  }
37
36
  }
37
+ }
38
38
 
39
- // Toast
40
- > .card {
41
-
42
- text-align: left;
43
- max-width: 450px;
44
- z-index: 999;
45
- cursor: pointer;
46
- padding-right: @spacing * 1.5;
47
-
48
- animation: aff-toast 0.1s ease;
49
- @keyframes aff-toast {
50
- 0% {
51
- opacity: 0.5;
52
- transform: scale(0.5);
53
- }
54
- 100% {
55
- opacity: 1;
56
- transform: scale(1);
57
- }
58
- }
59
-
60
- > i {
61
- color: @c1;
62
- flex: 0 0 1em;
63
- }
64
-
65
- h2 {
66
- font-size: 1em;
67
- }
39
+ #modals {
68
40
 
69
- p {
70
- text-align: left;
71
- }
41
+ &,
42
+ > .modal {
43
+ top: 0px;
72
44
  }
73
45
 
74
46
  > .modal {
@@ -136,6 +108,43 @@
136
108
  }
137
109
  }
138
110
 
111
+ #toasts {
112
+ // Toast
113
+ > .card {
114
+
115
+ text-align: left;
116
+ max-width: 450px;
117
+ z-index: 999;
118
+ cursor: pointer;
119
+ padding-right: @spacing * 1.5;
120
+
121
+ animation: aff-toast 0.1s ease;
122
+ @keyframes aff-toast {
123
+ 0% {
124
+ opacity: 0.5;
125
+ transform: scale(0.5);
126
+ }
127
+ 100% {
128
+ opacity: 1;
129
+ transform: scale(1);
130
+ }
131
+ }
132
+
133
+ > i {
134
+ color: @c1;
135
+ flex: 0 0 1em;
136
+ }
137
+
138
+ h2 {
139
+ font-size: 1em;
140
+ }
141
+
142
+ p {
143
+ text-align: left;
144
+ }
145
+ }
146
+ }
147
+
139
148
  // Selecteur moins profond pour que les clases utilitaires (w-a-x) soient prioritaires
140
149
  .modal > .card {
141
150
  max-width: 500px;
@@ -158,6 +158,9 @@ export default function useForm<TFormData extends {}>(
158
158
  if (fields.current === null || Object.keys(schema).join(',') !== Object.keys(fields.current).join(',')) {
159
159
  fields.current = {}
160
160
  for (const fieldName in schema.fields) {
161
+
162
+ const validator = schema.fields[fieldName];
163
+
161
164
  fields.current[fieldName] = {
162
165
 
163
166
  // Value control
@@ -182,8 +185,8 @@ export default function useForm<TFormData extends {}>(
182
185
 
183
186
  // Error
184
187
  errors: state.errors[fieldName],
185
- required: schema.fields[fieldName].options?.opt !== true,
186
- validator: schema.fields[fieldName]
188
+ required: validator.options?.opt !== true,
189
+ validator: validator,
187
190
  }
188
191
  }
189
192
  }
@@ -4,12 +4,13 @@
4
4
 
5
5
  // Npm
6
6
  import React from 'react';
7
- import type { ComponentChild, RefObject } from 'preact';
7
+ import type { JSX, ComponentChild, RefObject } from 'preact';
8
8
  import type { StateUpdater } from 'preact/hooks';
9
9
 
10
10
  // Core
11
11
  import Button from '@client/components/button';
12
12
  import Input from '@client/components/inputv3';
13
+ import type { TDialogControls } from '@client/components/dropdown';
13
14
 
14
15
  /*----------------------------------
15
16
  - TYPES
@@ -42,7 +43,7 @@ export type Props = (
42
43
  required?: boolean,
43
44
  noneSelection?: false | string,
44
45
  currentList: Choice[],
45
- refPopover?: RefObject<HTMLElement>
46
+ refDropdown?: RefObject<TDialogControls>
46
47
  }
47
48
 
48
49
  /*----------------------------------
@@ -53,7 +54,7 @@ export type Props = (
53
54
  - we don't want the selector to be rendered before the dropdown content is dhown
54
55
  - this component is called multiple time
55
56
  */
56
- export default ({
57
+ export default React.forwardRef<HTMLDivElement, Props>(({
57
58
  choices: initChoices,
58
59
  validator,
59
60
  required,
@@ -64,8 +65,9 @@ export default ({
64
65
  inline,
65
66
  multiple,
66
67
  currentList,
67
- refPopover
68
- }: Props) => {
68
+ refDropdown,
69
+ ...otherProps
70
+ }: Props, ref) => {
69
71
 
70
72
  /*----------------------------------
71
73
  - INIT
@@ -102,7 +104,7 @@ export default ({
102
104
  - RENDER
103
105
  ----------------------------------*/
104
106
  return (
105
- <div class={(inline ? '' : 'card ') + "col al-top"} ref={refPopover}>
107
+ <div {...otherProps} className={(inline ? '' : 'card ') + "col al-top " + (otherProps.className || '')} ref={ref}>
106
108
 
107
109
  {enableSearch && (
108
110
  <Input icon="search"
@@ -113,21 +115,6 @@ export default ({
113
115
  />
114
116
  )}
115
117
 
116
- {currentList.length !== 0 && (
117
- <ul class="col menu">
118
- {currentList.map(choice => (
119
- <Button size="s" onClick={() => {
120
- onChange( current => multiple
121
- ? current.filter(c => c.value !== choice.value)
122
- : undefined
123
- );
124
- }} suffix={<i src="check" class="fg primary" />}>
125
- {choice.label}
126
- </Button>
127
- ))}
128
- </ul>
129
- )}
130
-
131
118
  {choices === null ? (
132
119
  <div class="row h-3 al-center">
133
120
  <i src="spin" />
@@ -136,15 +123,28 @@ export default ({
136
123
  <ul class="col menu">
137
124
  {choices.map( choice => {
138
125
  const isCurrent = currentList.some(c => c.value === choice.value);
139
- return !isCurrent && (
126
+ return (
140
127
  <li>
141
128
  <Button size="s" onClick={() => {
142
- onChange( current => {
143
- return multiple
144
- ? [...(current || []), choice]
145
- : choice
146
- });
147
- }}>
129
+ onChange( current => {
130
+
131
+ console.log("click select", current, multiple, choice);
132
+
133
+ return multiple
134
+ ? (isCurrent
135
+ ? current.filter(c => c.value !== choice.value)
136
+ : [...(current || []), choice]
137
+ )
138
+ : (isCurrent
139
+ ? undefined
140
+ : choice
141
+ )
142
+ });
143
+
144
+ if (!multiple)
145
+ refDropdown?.current?.close(true);
146
+
147
+ }} suffix={ isCurrent && <i src="check" class="fg primary" /> }>
148
148
  {/*search.keywords ? (
149
149
  <span>
150
150
 
@@ -169,4 +169,4 @@ export default ({
169
169
  )}
170
170
  </div>
171
171
  )
172
- }
172
+ })
@@ -6,7 +6,7 @@
6
6
  import React from 'react';
7
7
 
8
8
  // Core
9
- import Dropdown, { TDialogControls, Props as DropdownProps } from '@client/components/dropdown';
9
+ import Dropdown, { TDropdownControl, Props as DropdownProps } from '@client/components/dropdown';
10
10
 
11
11
  // Specific
12
12
  import ChoiceSelector, {
@@ -36,7 +36,7 @@ export default ({
36
36
  - INIT
37
37
  ----------------------------------*/
38
38
 
39
- const refModal = React.useRef<TDialogControls>(null);
39
+ const refDropdown = React.useRef<TDropdownControl>(null);
40
40
 
41
41
  /*----------------------------------
42
42
  - ACTIONS
@@ -56,9 +56,9 @@ export default ({
56
56
  {props.inline ? (
57
57
  <ChoiceSelector {...props} currentList={currentList} />
58
58
  ) : (
59
- <Dropdown {...props} content={(() =>
60
- <ChoiceSelector {...props} currentList={currentList} />
61
- )} iconR="chevron-down" refModal={refModal}>
59
+ <Dropdown {...props} content={(
60
+ <ChoiceSelector {...props} currentList={currentList} refDropdown={refDropdown} />
61
+ )} iconR="chevron-down" refDropdown={refDropdown}>
62
62
 
63
63
  {currentList.length === 0
64
64
  ? title
@@ -1,6 +1,6 @@
1
1
  export type TSide = "left"|"top"|"right"|"bottom";
2
2
 
3
- const debug = true;
3
+ const debug = false;
4
4
 
5
5
  export type TPosition = ReturnType<typeof corrigerPosition>
6
6
 
@@ -129,7 +129,7 @@ export default function corrigerPosition(
129
129
  debug && console.log(`[popover] Left: Conservation`, posFinale.left);
130
130
  }
131
131
 
132
- console.log({ posInit, dimsPop, frontieres }, { posFinale });
132
+ debug && console.log({ posInit, dimsPop, frontieres }, { posFinale });
133
133
 
134
134
  return {
135
135
  css: posFinale,
@@ -3,7 +3,8 @@
3
3
  ----------------------------------*/
4
4
 
5
5
  // Npm
6
- import React, { JSX, ComponentChild } from 'react';
6
+ import React from 'react';
7
+ import { JSX, ComponentChild } from 'preact';
7
8
  import type { StateUpdater } from 'preact/hooks';
8
9
 
9
10
  // Libs
@@ -15,11 +16,11 @@ import useContexte from '@/client/context';
15
16
  - TYPES
16
17
  ----------------------------------*/
17
18
 
18
- export type Props = {
19
+ export type Props = JSX.HTMLAttributes<HTMLDivElement> & {
19
20
  id?: string,
20
21
 
21
22
  // Display
22
- content?: JSX.Element | (() => JSX.Element),
23
+ content?: JSX.Element,
23
24
  state: [boolean, StateUpdater<boolean>],
24
25
  width?: number | string,
25
26
  disable?: boolean
@@ -53,7 +54,7 @@ export default (props: Props) => {
53
54
  ...autresProps
54
55
  } = props;
55
56
 
56
- const [position, setPosition] = React.useState<TPosition>(undefined);
57
+ const [position, setPosition] = React.useState<TPosition | undefined>(undefined);
57
58
  const refCont = React.useRef<HTMLElement>(null);
58
59
  const refContent = React.useRef<HTMLElement>(null);
59
60
 
@@ -61,7 +62,7 @@ export default (props: Props) => {
61
62
 
62
63
  // Màj visibilite
63
64
  React.useEffect(() => {
64
- if (shown === true) {
65
+ if (shown === true && refContent.current !== null && refCont.current !== null) {
65
66
 
66
67
  // Positionnement si affichage
67
68
  setPosition(
@@ -70,7 +71,7 @@ export default (props: Props) => {
70
71
  refContent.current,
71
72
  false,
72
73
  side,
73
- frame || document.getElementById('page')
74
+ frame || document.getElementById('page') || undefined
74
75
  )
75
76
  );
76
77
 
@@ -100,17 +101,20 @@ export default (props: Props) => {
100
101
 
101
102
  let renderedContent: ComponentChild;
102
103
  if (active) {
103
- content = typeof content === 'function' ? React.createElement(content) : content;
104
+ //content = typeof content === 'function' ? React.createElement(content) : content;
105
+ console.log("render content", content);
104
106
  renderedContent = React.cloneElement(
105
107
  content,
106
108
  {
107
109
  className: (content.props.className || '')
108
110
  + ' card white popover pd-1'
109
111
  + (position ? ' pos_' + position.cote : ''),
112
+
110
113
  ref: (ref: any) => {
111
114
  if (ref !== null)
112
115
  refContent.current = ref
113
116
  },
117
+
114
118
  style: {
115
119
  ...(content.props.style || {}),
116
120
  ...(position ? {
@@ -8,18 +8,19 @@ import { ComponentChild, RefObject } from 'preact';
8
8
 
9
9
  // Core
10
10
  import Button, { Props as ButtonProps } from '../button';
11
- import { TDialogControls } from '../Dialog/Manager';
12
11
  import Popover from '../containers/Popover';
13
12
 
14
13
  /*----------------------------------
15
14
  - TYPES
16
15
  ----------------------------------*/
17
16
 
18
- export type { TDialogControls } from '../Dialog/Manager';
19
-
20
17
  export type Props = ButtonProps & {
21
18
  content: ComponentChild | (() => ComponentChild),
22
- refModal?: RefObject<TDialogControls>
19
+ refDropdown?: RefObject<TDropdownControl>
20
+ }
21
+
22
+ export type TDropdownControl = {
23
+ close: () => void
23
24
  }
24
25
 
25
26
  /*----------------------------------
@@ -29,7 +30,7 @@ export default (props: Props) => {
29
30
 
30
31
  let {
31
32
  content,
32
- refModal,
33
+ refDropdown,
33
34
  ...buttonProps
34
35
  } = props;
35
36
 
@@ -37,6 +38,11 @@ export default (props: Props) => {
37
38
 
38
39
  const refButton = React.useRef<HTMLElement>(null);
39
40
 
41
+ if (refDropdown)
42
+ refDropdown.current = {
43
+ close: () => popoverState[1]( false )
44
+ }
45
+
40
46
  return (
41
47
  <Popover content={content} state={popoverState}>
42
48
  <Button {...buttonProps} refElem={refButton} />
@@ -10,7 +10,7 @@ import { ComponentChild } from 'preact';
10
10
  import Button, { Props as ButtonProps } from '../button';
11
11
 
12
12
  // Libs
13
- import { TPopoverControls, PopoverProps } from './Manager';
13
+ import { TPopover, PopoverProps } from './Manager';
14
14
  import useContexte from '@client/context';
15
15
 
16
16
  /*----------------------------------
@@ -77,7 +77,7 @@ export default (props: Props) => {
77
77
  }
78
78
 
79
79
  const refButton = React.useRef<HTMLElement>(null);
80
- const refPopover = React.useRef<TPopoverControls>(null);
80
+ const refPopover = React.useRef<TPopover>(null);
81
81
 
82
82
  let classe = buttonProps.class === undefined
83
83
  ? "dropdown"
@@ -49,6 +49,7 @@
49
49
 
50
50
  height: @labelH;
51
51
  font-size: 0.8em;
52
+ line-height: 0.8em;
52
53
  color: var(--cTxtDesc);
53
54
  transition: all .1s ease-out;
54
55
  }
@@ -4,11 +4,13 @@
4
4
 
5
5
  // Npm
6
6
  import React from 'react';
7
- import { ComponentChild, JSX } from 'preact';
7
+ import { VNode, JSX } from 'preact';
8
8
  import TextareaAutosize from 'react-textarea-autosize';
9
9
 
10
10
  // Core libs
11
11
  import { useInput, InputBaseProps } from './base';
12
+ import { default as Validator } from '../../../common/validation/validator';
13
+ import type SchemaValidators from '@common/validation/validators';
12
14
 
13
15
  /*----------------------------------
14
16
  - TYPES
@@ -17,20 +19,25 @@ export type Props = {
17
19
 
18
20
  // Decoration
19
21
  icon?: string,
20
- prefix?: React.VNode,
21
- suffix?: React.VNode,
22
+ prefix?: VNode,
23
+ suffix?: VNode,
22
24
  iconR?: string,
23
25
 
24
26
  // State
25
27
  inputRef?: React.Ref<HTMLInputElement>
26
28
 
27
- // Behavior
28
- type?: 'email' | 'password' | 'longtext' | 'number',
29
- choice?: string[] | ((input: string) => Promise<string[]>),
30
-
31
29
  // Actions
32
30
  onPressEnter?: (value: string) => void,
33
- }
31
+ } & ({
32
+ type?: 'email' | 'password' | 'longtext'
33
+ validator?: Validator<string>,
34
+
35
+ // Behavior
36
+ choice?: string[] | ((input: string) => Promise<string[]>),
37
+ } | {
38
+ type: 'number',
39
+ validator?: ReturnType< SchemaValidators["number"] >,
40
+ })
34
41
 
35
42
  /*----------------------------------
36
43
  - COMPOSANT
@@ -41,7 +48,7 @@ export default ({
41
48
  // State
42
49
  inputRef, errors,
43
50
  // Behavior
44
- type, choice,
51
+ type, choice, validator,
45
52
  // Actions
46
53
  onPressEnter,
47
54
  ...props
@@ -104,7 +111,7 @@ export default ({
104
111
  suffix = <i src={iconR} />
105
112
 
106
113
  // When no value, show the lable as a placeholder
107
- if (value === '')
114
+ if (value === '' || value === undefined)
108
115
  className += ' empty';
109
116
  if (focus)
110
117
  className += ' focus';
@@ -114,6 +121,21 @@ export default ({
114
121
  if (props.className !== undefined)
115
122
  className += ' ' + props.className;
116
123
 
124
+ /*----------------------------------
125
+ - VALIDATION
126
+ ----------------------------------*/
127
+
128
+ // Map vaidation options to input props
129
+ if (validator?.options) {
130
+ if (type === 'number') {
131
+ ({
132
+ min: fieldProps.min,
133
+ max: fieldProps.max,
134
+ steps: fieldProps.steps,
135
+ } = validator.options)
136
+ }
137
+ }
138
+
117
139
  /*----------------------------------
118
140
  - RENDER
119
141
  ----------------------------------*/
@@ -69,11 +69,9 @@ export const blurable = (...args: [HTMLElement, Function][]) => {
69
69
 
70
70
  export const focusContent = ( container: HTMLElement ) => {
71
71
 
72
- const toFocus = container.querySelector(
72
+ const toFocus = container.querySelector<HTMLInputElement | HTMLTextAreaElement | HTMLButtonElement>(
73
73
  'input, textarea, button.btn.primary, footer > button.btn'
74
74
  ) || container;
75
75
 
76
- console.log('Element to focus', toFocus);
77
- // TODO: Type only docusable elemnts
78
76
  toFocus?.focus();
79
77
  }
@@ -41,7 +41,7 @@ const raccourcisMime = {
41
41
  /*----------------------------------
42
42
  - CLASS
43
43
  ----------------------------------*/
44
- export default class SchemaValidator {
44
+ export default class SchemaValidators {
45
45
 
46
46
  /*----------------------------------
47
47
  - CONTENEURS
@@ -185,6 +185,7 @@ export default class SchemaValidator {
185
185
  public number = (withDecimals: boolean) => ({ ...opts }: TValidator<number> & {
186
186
  min?: number,
187
187
  max?: number,
188
+ step?: number,
188
189
  } = {}) => new Validator<number>('number', (val, input, output, corriger?: boolean) => {
189
190
 
190
191
  // Vérifications suivantes inutiles si des values spécifiques ont été fournies
@@ -68,6 +68,7 @@ export default abstract class Application extends Service<Config, Hooks, /* TODO
68
68
  public: process.cwd() + '/public',
69
69
 
70
70
  // TODO: move to disk
71
+ var: process.cwd() + '/var',
71
72
  typings: process.cwd() + '/var/typings',
72
73
  cache: process.cwd() + '/var/cache',
73
74
  data: process.cwd() + '/var/data',
@@ -190,10 +190,10 @@ export default class Console extends Service<Config, Hooks, Application> {
190
190
  }
191
191
 
192
192
  private clean() {
193
- this.config.debug && console.log(LogPrefix, `Clean logs buffer. Current size:`, this.logs.length, '/', this.config.bufferLimit);
193
+ /*this.config.debug && console.log(LogPrefix, `Clean logs buffer. Current size:`, this.logs.length, '/', this.config.bufferLimit);
194
194
  const bufferOverflow = this.logs.length - this.config.bufferLimit;
195
195
  if (bufferOverflow > 0)
196
- this.logs = this.logs.slice(bufferOverflow);
196
+ this.logs = this.logs.slice(bufferOverflow);*/
197
197
  }
198
198
 
199
199
  /*----------------------------------
@@ -213,6 +213,8 @@ export default class Console extends Service<Config, Hooks, Application> {
213
213
  console.error(`Error caused by this query:`, printedQuery);
214
214
  }
215
215
  console.error(LogPrefix, `Sending bug report for the following error:`, error);
216
+ if (error.dataForDebugging !== undefined)
217
+ console.error(LogPrefix, `More data about the error:`, error.dataForDebugging);
216
218
 
217
219
  // Prevent spamming the mailbox if infinite loop
218
220
  const bugId = ['server', request?.user?.name, undefined, error.message].filter(e => !!e).join('::');
@@ -230,7 +232,7 @@ export default class Console extends Service<Config, Hooks, Application> {
230
232
  // On envoi l'email avant l'insertion dans bla bdd
231
233
  // Car cette denrière a plus de chances de provoquer une erreur
232
234
  const logsHtml = this.printHtml(
233
- this.logs.filter(e => e.channelId === channelId),
235
+ this.logs/*.filter(e => e.channelId === channelId)*/.slice(-100),
234
236
  true
235
237
  );
236
238
 
@@ -372,7 +374,7 @@ export default class Console extends Service<Config, Hooks, Application> {
372
374
  }
373
375
  }
374
376
 
375
- return this.printHtml( entries.reverse() );
377
+ return this.printHtml( entries );
376
378
  }
377
379
 
378
380
  public printHtml(logs: TLog[], full: boolean = false): string {
@@ -0,0 +1,86 @@
1
+ /*----------------------------------
2
+ - DEPS
3
+ ----------------------------------*/
4
+
5
+ // Core
6
+ import Application, { Service } from '@server/app';
7
+
8
+ /*----------------------------------
9
+ - CONFIG
10
+ ----------------------------------*/
11
+
12
+ export type THooks = {
13
+
14
+ }
15
+
16
+ /*----------------------------------
17
+ - TYPE
18
+ ----------------------------------*/
19
+
20
+ export type TDrivercnfig = {
21
+
22
+ debug: boolean,
23
+
24
+ rootDir: string,
25
+ buckets: {
26
+ [id: string]: string
27
+ }
28
+ }
29
+
30
+ export type SourceFile = {
31
+ name: string,
32
+ path: string,
33
+ modified: number,
34
+ parentFolder: string,
35
+ source: string
36
+ }
37
+
38
+ export type TOutputFileOptions = {
39
+ encoding: string
40
+ }
41
+
42
+ export type TReadFileOptions = {
43
+ encoding?: 'string'|'buffer',
44
+ withMetas?: boolean
45
+ }
46
+
47
+ /*----------------------------------
48
+ - CLASS
49
+ ----------------------------------*/
50
+
51
+ export default abstract class FsDriver<
52
+ Config extends TDrivercnfig = TDrivercnfig,
53
+ TBucketName = keyof Config["buckets"]
54
+ > {
55
+
56
+ public constructor( public app: Application, public config: Config ) {
57
+
58
+ }
59
+
60
+ public abstract mount(): Promise<void>;
61
+
62
+ public abstract readDir( bucketName: TBucketName, dirname?: string ): Promise<SourceFile[]>;
63
+
64
+ public abstract readFile(
65
+ bucketName: TBucketName,
66
+ filename: string,
67
+ options: TReadFileOptions
68
+ ): Promise<string>;
69
+
70
+ public abstract createReadStream( bucketName: TBucketName, filename: string );
71
+
72
+ public abstract exists( bucketName: TBucketName, filename: string ): Promise<boolean>;
73
+
74
+ public abstract move( bucketName: TBucketName, source: string, destination: string, options: { overwrite?: boolean }): Promise<void>;
75
+
76
+ public abstract outputFile( bucketName: TBucketName, filename: string, content: string, encoding: TOutputFileOptions ): Promise<{
77
+ path: string
78
+ }>;
79
+
80
+ public abstract readJSON( bucketName: TBucketName, filename: string ): Promise<any>;
81
+
82
+ public abstract delete( bucketName: TBucketName, filename: string ): Promise<boolean>;
83
+
84
+ public abstract unmount(): Promise<void>;
85
+
86
+ }
@@ -0,0 +1,63 @@
1
+ /*----------------------------------
2
+ - DEPS
3
+ ----------------------------------*/
4
+
5
+ // Core
6
+ import Application, { Service } from '@server/app';
7
+
8
+ // Specific
9
+ import type Driver from './driver';
10
+
11
+ /*----------------------------------
12
+ - TYPES
13
+ ----------------------------------*/
14
+
15
+ type TMountpointList = { [name: string]: Driver }
16
+
17
+ type Config<MountpointList extends TMountpointList> = {
18
+ default: keyof MountpointList,
19
+ }
20
+
21
+ export type Hooks = {
22
+
23
+ }
24
+
25
+ /*----------------------------------
26
+ - SERVICE
27
+ ----------------------------------*/
28
+ export default class DisksManager<
29
+ MountpointList extends TMountpointList = {},
30
+ TConfig extends Config<MountpointList> = Config<MountpointList>,
31
+ TApplication extends Application = Application
32
+ > extends Service<TConfig, Hooks, TApplication> {
33
+
34
+ public default: Driver;
35
+
36
+ public constructor(
37
+ public app: TApplication,
38
+ public config: TConfig,
39
+ public mounted: MountpointList
40
+ ) {
41
+
42
+ super(app, config);
43
+
44
+ if (Object.keys( mounted ).length === 0)
45
+ throw new Error("At least one disk should be mounted.");
46
+
47
+ const defaultDisk = mounted[ config.default ];
48
+ if (defaultDisk === undefined)
49
+ console.log(`Default disk "${config.default as string}" not mounted.`);
50
+
51
+ this.default = defaultDisk;
52
+
53
+ }
54
+
55
+ public async register() {
56
+
57
+ }
58
+
59
+ public async start() {
60
+
61
+ }
62
+
63
+ }
@@ -93,7 +93,7 @@ export default class FetchService extends Service<Config, Hooks, Application> {
93
93
 
94
94
  // Convert to webp and finalize
95
95
  const processedBuffer = await processing.webp({ quality }).toBuffer().catch(e => {
96
- console.error(LogPrefix, `Error while processing image at ${imageBuffer}:`, e);
96
+ console.error(LogPrefix, `Error while processing image at ${imageFileUrl}:`, e);
97
97
  return null;
98
98
  })
99
99
 
@@ -21,6 +21,7 @@ import type { GlobImportedWithMetas } from 'babel-plugin-glob-import';
21
21
  // Core
22
22
  import Application, { Service } from '@server/app';
23
23
  import context from '@server/context';
24
+ import type DiskDriver from '@server/services/disks/driver';
24
25
  import { CoreError, NotFound } from '@common/errors';
25
26
  import BaseRouter, {
26
27
  TRoute, TErrorRoute, TRouteModule,
@@ -88,6 +89,8 @@ export type Config<
88
89
 
89
90
  debug: boolean,
90
91
 
92
+ disk: DiskDriver,
93
+
91
94
  http: HttpServiceConfig
92
95
 
93
96
  // Set it as a function, so when we instanciate the services, we can callthis.router to pass the router instance in roiuter services
@@ -282,7 +285,7 @@ export default class ServerRouter<
282
285
  //await TrackingService.LoadCache();
283
286
 
284
287
  // Generate typescript typings
285
- if (this.app.env.profile = 'dev')
288
+ if (this.app.env.profile === 'dev')
286
289
  this.genTypings();
287
290
 
288
291
  // Ordonne par ordre de priorité
@@ -550,11 +553,12 @@ declare type Routes = {
550
553
  // Rapport / debug
551
554
  if (code === 500) {
552
555
 
556
+ // Print the error here so the stacktrace appears in the bug report logs
557
+ console.log(LogPrefix, "Error catched from the router:", e);
558
+
553
559
  // Report error
554
560
  await this.app.runHook('error', e, request);
555
561
 
556
- console.log("ERROR 500 VIA ROUTER", e);
557
-
558
562
  // Don't exose technical errors to users
559
563
  if (this.app.env.profile === 'prod')
560
564
  e.message = "We encountered an internal error, and our team has just been notified. Sorry for the inconvenience.";
@@ -8,7 +8,6 @@
8
8
  ----------------------------------*/
9
9
 
10
10
  // Npm
11
- import fs from 'fs-extra';
12
11
  import express from 'express';
13
12
 
14
13
  // Core
@@ -241,28 +240,29 @@ export default class ServerResponse<
241
240
  }
242
241
 
243
242
  // TODO: https://github.com/adonisjs/http-server/blob/develop/src/Response/index.ts#L430
244
- public file( fichier: string ) {
243
+ public async file( fichier: string ) {
245
244
 
246
245
  // Securité
247
246
  if (fichier.includes('..'))
248
247
  throw new Forbidden("Disallowed");
249
248
 
250
- // Force absolute path
251
- if (!fichier.startsWith( this.app.path.root ))
252
- fichier = fichier[0] === '/'
253
- ? this.app.path.root + '/bin' + fichier
254
- : this.app.path.data + '/' + fichier;
249
+ // // Force absolute path
250
+ // if (!fichier.startsWith( this.app.path.root ))
251
+ // fichier = fichier[0] === '/'
252
+ // ? this.app.path.root + '/bin' + fichier
253
+ // : this.app.path.data + '/' + fichier;
255
254
 
256
- console.log(`[response] Serving file "${fichier}"`);
255
+ const disk = this.router.config.disk;
257
256
 
258
257
  // Verif existance
259
- if (!fs.existsSync(fichier)) {
258
+ const fileExists = await disk.exists('data', fichier);
259
+ if (!fileExists) {
260
260
  console.log("File " + fichier + " was not found.");
261
261
  throw new NotFound();
262
262
  }
263
263
 
264
264
  // envoi fichier
265
- this.data = fs.readFileSync(fichier);
265
+ this.data = await disk.readFile('data', fichier);
266
266
  return this.end();
267
267
  }
268
268
 
@@ -3,7 +3,7 @@
3
3
  ----------------------------------*/
4
4
 
5
5
  // Nodejs
6
- import crypto from 'crypto';
6
+ import crypto, { Encoding } from 'crypto';
7
7
 
8
8
  // Core
9
9
  import Application, { Service } from '@server/app';
@@ -20,7 +20,11 @@ import { Forbidden } from '@common/errors';
20
20
 
21
21
  export type Config = {
22
22
  debug?: boolean,
23
+ // Initialisation vector
24
+ // Generate one here: https://www.allkeysgenerator.com/Random/Security-Encryption-Key-Generator.aspx
23
25
  iv: string,
26
+ // Define usage-specific keys
27
+ // You can also generate one via the link upper
24
28
  keys: {[keyName: string]: string}
25
29
  }
26
30
 
@@ -28,6 +32,14 @@ export type Hooks = {
28
32
 
29
33
  }
30
34
 
35
+ type TEncryptOptions = {
36
+ encoding: Encoding
37
+ }
38
+
39
+ type TDecryptOptions = {
40
+ encoding: Encoding
41
+ }
42
+
31
43
  /*----------------------------------
32
44
  - SERVICE
33
45
  ----------------------------------*/
@@ -41,27 +53,31 @@ export default class AES<TConfig extends Config = Config> extends Service<TConfi
41
53
 
42
54
  }
43
55
 
44
- public encrypt( keyName: keyof TConfig["keys"], data: any ) {
56
+ public encrypt( keyName: keyof TConfig["keys"], data: any, options: TEncryptOptions = {
57
+ encoding: 'base64url'
58
+ }) {
45
59
 
46
60
  const encKey = this.config.keys[ keyName as keyof typeof this.config.keys ];
47
61
 
48
62
  data = JSON.stringify(data);
49
63
 
50
64
  let cipher = crypto.createCipheriv('aes-256-cbc', encKey, this.config.iv);
51
- let encrypted = cipher.update(data, 'utf8', 'base64');
52
- encrypted += cipher.final('base64');
65
+ let encrypted = cipher.update(data, 'utf8', options.encoding);
66
+ encrypted += cipher.final(options.encoding);
53
67
  return encrypted;
54
68
 
55
69
  }
56
70
 
57
- public decrypt( keyName: keyof TConfig["keys"], data: string ) {
71
+ public decrypt( keyName: keyof TConfig["keys"], data: string, options: TDecryptOptions = {
72
+ encoding: 'base64url'
73
+ }) {
58
74
 
59
75
  const encKey = this.config.keys[ keyName as keyof typeof this.config.keys ];
60
76
 
61
77
  try {
62
78
 
63
79
  let decipher = crypto.createDecipheriv('aes-256-cbc', encKey, this.config.iv);
64
- let decrypted = decipher.update(data, 'base64', 'utf8');
80
+ let decrypted = decipher.update(data, options.encoding, 'utf8');
65
81
  return JSON.parse(decrypted + decipher.final('utf8'));
66
82
 
67
83
  } catch (error) {