underpost 2.7.94 → 2.8.0

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.
@@ -43,6 +43,7 @@
43
43
  "lampp",
44
44
  "loadingio",
45
45
  "Longname",
46
+ "metanarrative",
46
47
  "Microdata",
47
48
  "minami",
48
49
  "MMORPG",
@@ -60,6 +61,8 @@
60
61
  "Tokenomics",
61
62
  "underpost",
62
63
  "Unequip",
64
+ "uuidv",
65
+ "Valkey",
63
66
  "webroot",
64
67
  "xampp",
65
68
  "xfwd",
package/bin/deploy.js CHANGED
@@ -35,6 +35,7 @@ import { Lampp } from '../src/runtime/lampp/Lampp.js';
35
35
  import { DefaultConf } from '../conf.js';
36
36
  import { JSONweb } from '../src/server/client-formatted.js';
37
37
  import ejs from 'easy-json-schema';
38
+ import { Xampp } from '../src/runtime/xampp/Xampp.js';
38
39
 
39
40
  const logger = loggerFactory(import.meta);
40
41
 
@@ -283,6 +284,28 @@ try {
283
284
  }
284
285
  break;
285
286
 
287
+ case 'xampp': {
288
+ const directory = 'c:/xampp/htdocs';
289
+ const host = 'localhost';
290
+ const port = 80;
291
+ Xampp.removeRouter();
292
+ Xampp.appendRouter(` Listen ${port}
293
+ <VirtualHost *:${port}>
294
+ DocumentRoot "${directory}"
295
+ ServerName ${host}:${port}
296
+
297
+ <Directory "${directory}">
298
+ Options Indexes FollowSymLinks MultiViews
299
+ AllowOverride All
300
+ Require all granted
301
+ </Directory>
302
+
303
+ </VirtualHost>
304
+ `);
305
+ if (Xampp.enabled() && Xampp.router) Xampp.initService({ daemon: true });
306
+ break;
307
+ }
308
+
286
309
  case 'adminer': {
287
310
  const directory = '/dd/engine/public/adminer';
288
311
  // const host = '127.0.0.1';
@@ -998,6 +1021,22 @@ ${uniqueArray(logs.all.map((log) => `- ${log.author_name} ([${log.author_email}]
998
1021
  break;
999
1022
  }
1000
1023
 
1024
+ case 'valkey': {
1025
+ if (!process.argv.includes('server')) {
1026
+ shellExec(`cd /dd && git clone https://github.com/valkey-io/valkey.git`);
1027
+ shellExec(`cd /dd/valkey && make`);
1028
+ shellExec(`apt install valkey-tools`); // valkey-cli
1029
+ }
1030
+ shellExec(`cd /dd/valkey && ./src/valkey-server`);
1031
+
1032
+ break;
1033
+ }
1034
+
1035
+ case 'valkey-service': {
1036
+ shellExec(`pm2 start bin/deploy.js --node-args=\"--max-old-space-size=8192\" --name valkey -- valkey server`);
1037
+ break;
1038
+ }
1039
+
1001
1040
  default:
1002
1041
  break;
1003
1042
  }
package/bin/index.js CHANGED
@@ -19,7 +19,7 @@ const globalBinFolder = `${shellExec(`npm root -g`, {
19
19
 
20
20
  const program = new Command();
21
21
 
22
- const version = '2.7.94';
22
+ const version = '2.8.0';
23
23
 
24
24
  program.name('underpost').description(`underpost.net ci/cd cli ${version}`).version(version);
25
25
 
@@ -58,7 +58,7 @@ services:
58
58
  cpus: '0.25'
59
59
  memory: 20M
60
60
  labels: # labels in Compose file instead of Dockerfile
61
- engine.version: '2.7.94'
61
+ engine.version: '2.8.0'
62
62
  networks:
63
63
  - load-balancer
64
64
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "type": "module",
3
3
  "main": "src/index.js",
4
4
  "name": "underpost",
5
- "version": "2.7.94",
5
+ "version": "2.8.0",
6
6
  "description": "pwa api rest template",
7
7
  "scripts": {
8
8
  "start": "env-cmd -f .env.production node --max-old-space-size=8192 src/server",
@@ -11,7 +11,7 @@
11
11
  "pm2-delete": "pm2 delete engine",
12
12
  "build": "node bin/deploy build-full-client",
13
13
  "build-production": "env-cmd -f .env.production node bin/deploy build-full-client",
14
- "dev": "env-cmd -f .env.development node src/client.dev --no-warnings",
14
+ "dev": "env-cmd -f .env.development node src/client.dev",
15
15
  "dev-api": "env-cmd -f .env.development nodemon --watch src --ignore src/client src/api",
16
16
  "docs": "jsdoc -c jsdoc.json",
17
17
  "backup": "node bin/db default.net/ export",
@@ -117,7 +117,8 @@
117
117
  "systeminformation": "^5.21.17",
118
118
  "uglify-js": "^3.17.4",
119
119
  "validator": "^13.11.0",
120
- "winston": "^3.11.0"
120
+ "winston": "^3.11.0",
121
+ "iovalkey": "^0.2.1"
121
122
  },
122
123
  "devDependencies": {
123
124
  "clean-jsdoc-theme": "^4.3.0",
@@ -58,7 +58,7 @@ const UserDto = {
58
58
  },
59
59
  },
60
60
  auth: {
61
- payload: (user) => ({ _id: user._id.toString(), role: user.role }),
61
+ payload: (user) => ({ _id: user._id.toString(), role: user.role, email: user.email }),
62
62
  },
63
63
  };
64
64
 
@@ -6,24 +6,13 @@ import { CoreWsEmit } from '../../ws/core/core.ws.emit.js';
6
6
  import { CoreWsMailerChannel } from '../../ws/core/channels/core.ws.mailer.js';
7
7
  import validator from 'validator';
8
8
  import { DataBaseProvider } from '../../db/DataBaseProvider.js';
9
- import { s4 } from '../../client/components/core/CommonJs.js';
10
9
  import { FileFactory } from '../file/file.service.js';
11
- import fs from 'fs-extra';
12
- import { svg, png, png3x } from 'font-awesome-assets';
13
10
  import { UserDto } from './user.model.js';
14
- import Jimp from 'jimp';
11
+ import { selectDtoFactory, ValkeyAPI } from '../../server/valkey.js';
12
+ import { getDefaultProfileImageId } from '../../server/client-icons.js';
15
13
 
16
14
  const logger = loggerFactory(import.meta);
17
15
 
18
- const getDefaultProfileImageId = async (File) => {
19
- const faId = 'user';
20
- const tmpFilePath = `./tmp/${faId}-${s4() + s4()}.svg`;
21
- fs.writeFileSync(tmpFilePath, svg(faId, '#f5f5f5d1'), 'utf8');
22
- const file = await new File(FileFactory.svg(fs.readFileSync(tmpFilePath), `${faId}.svg`)).save();
23
- fs.removeSync(tmpFilePath);
24
- return file._id;
25
- };
26
-
27
16
  const UserService = {
28
17
  post: async (req, res, options) => {
29
18
  /** @type {import('./user.model.js').UserModel} */
@@ -235,6 +224,15 @@ const UserService = {
235
224
  }
236
225
  } else throw new Error('invalid email or password');
237
226
 
227
+ case 'guest': {
228
+ const user = await ValkeyAPI.valkeyObjectFactory('user', options);
229
+ await ValkeyAPI.setValkeyObject(user.email, user);
230
+ return {
231
+ token: hashJWT({ user: UserDto.auth.payload(user) }),
232
+ user: selectDtoFactory(user, UserDto.select.get()),
233
+ };
234
+ }
235
+
238
236
  default: {
239
237
  const validatePassword = validatePasswordMiddleware(req.body.password);
240
238
  if (validatePassword.status === 'error') throw new Error(validatePassword.message);
@@ -327,13 +325,15 @@ const UserService = {
327
325
  return await User.find().select(UserDto.select.getAll());
328
326
 
329
327
  case 'auth': {
330
- const user = await User.findOne({
331
- _id: req.auth.user._id,
332
- });
328
+ const user = (await ValkeyAPI.getValkeyObject(req.auth.user.email))
329
+ ? await ValkeyAPI.getValkeyObject(req.auth.user.email)
330
+ : await User.findOne({
331
+ _id: req.auth.user._id,
332
+ });
333
333
 
334
334
  const file = await File.findOne({ _id: user.profileImageId });
335
335
 
336
- if (!file) {
336
+ if (!file && !(await ValkeyAPI.getValkeyObject(req.auth.user.email))) {
337
337
  await User.findByIdAndUpdate(
338
338
  user._id,
339
339
  { profileImageId: await getDefaultProfileImageId(File) },
@@ -342,10 +342,11 @@ const UserService = {
342
342
  },
343
343
  );
344
344
  }
345
-
346
- return await User.findOne({
347
- _id: req.auth.user._id,
348
- }).select(UserDto.select.get());
345
+ return (await ValkeyAPI.getValkeyObject(req.auth.user.email))
346
+ ? selectDtoFactory(await ValkeyAPI.getValkeyObject(req.auth.user.email), UserDto.select.get())
347
+ : await User.findOne({
348
+ _id: req.auth.user._id,
349
+ }).select(UserDto.select.get());
349
350
  }
350
351
 
351
352
  default: {
@@ -1,11 +1,11 @@
1
1
  import { UserService } from '../../services/user/user.service.js';
2
+ import { Auth } from './Auth.js';
2
3
  import { BtnIcon } from './BtnIcon.js';
3
4
  import { newInstance, s4 } from './CommonJs.js';
4
5
  import { renderStatus, renderWave } from './Css.js';
5
6
  import { EventsUI } from './EventsUI.js';
6
7
  import { fileFormDataFactory, Input } from './Input.js';
7
8
  import { LogIn } from './LogIn.js';
8
- import { LogOut } from './LogOut.js';
9
9
  import { Modal } from './Modal.js';
10
10
  import { NotificationManager } from './NotificationManager.js';
11
11
  import { Translate } from './Translate.js';
@@ -21,19 +21,19 @@ const Account = {
21
21
  const waveAnimationId = 'account-wave';
22
22
  const profileFileAccept = ['image/png', 'image/jpeg'];
23
23
  setTimeout(async () => {
24
- if (LogIn.Scope.user.main.model.user.profileImage) {
25
- append(
26
- `.wave-animation-container-${waveAnimationId}`,
27
- html` <div class="abs center account-profile-image-container">
28
- <img
29
- class="abs center account-profile-image"
30
- style="opacity: 1"
31
- src="${LogIn.Scope.user.main.model.user.profileImage.imageSrc}"
32
- />
33
- </div>
34
- <div class="abs center account-profile-image-loading" style="color: white"></div>`,
35
- );
36
- }
24
+ append(
25
+ `.wave-animation-container-${waveAnimationId}`,
26
+ html` <div class="abs center account-profile-image-container">
27
+ <img
28
+ class="abs center account-profile-image"
29
+ style="opacity: 1"
30
+ ${LogIn.Scope.user.main.model.user.profileImage
31
+ ? `src="${LogIn.Scope.user.main.model.user.profileImage.imageSrc}"`
32
+ : ''}
33
+ />
34
+ </div>
35
+ <div class="abs center account-profile-image-loading" style="color: white"></div>`,
36
+ );
37
37
 
38
38
  const formData = [
39
39
  {
@@ -49,133 +49,141 @@ const Account = {
49
49
  rules: [{ type: 'isStrongPassword' }],
50
50
  },
51
51
  ];
52
- const validators = await Validator.instance(formData);
53
52
 
54
- for (const inputData of formData) {
55
- s(`.${inputData.id}`).value =
56
- !user[inputData.model] && inputData.defaultValue ? inputData.defaultValue : user[inputData.model];
57
- }
58
- let lastUser;
59
- const submit = async () => {
60
- lastUser = newInstance(user);
61
- const { errorMessage } = await validators();
62
- if (errorMessage) return;
63
- const body = {};
53
+ this.formData = formData;
54
+
55
+ this.instanceModalUiEvents = async ({ user }) => {
56
+ const validators = await Validator.instance(formData);
57
+
64
58
  for (const inputData of formData) {
65
- if (!s(`.${inputData.id}`).value || s(`.${inputData.id}`).value === 'undefined') continue;
66
- if ('model' in inputData) {
67
- body[inputData.model] = s(`.${inputData.id}`).value;
68
- user[inputData.model] = s(`.${inputData.id}`).value;
69
- }
59
+ s(`.${inputData.id}`).value =
60
+ !user[inputData.model] && inputData.defaultValue ? inputData.defaultValue : user[inputData.model];
70
61
  }
71
- const result = await UserService.put({ id: user._id, body });
72
- NotificationManager.Push({
73
- html:
74
- result.status === 'error' && result.message
75
- ? result.message
76
- : Translate.Render(`${result.status}-update-user`),
77
- status: result.status,
78
- });
79
- if (result.status === 'success') {
80
- user = result.data;
81
- this.triggerUpdateEvent({ user });
82
- if (lastUser.emailConfirmed !== user.emailConfirmed) {
83
- this.renderVerifyEmailStatus(user);
84
- }
62
+ let lastUser;
63
+ const submit = async () => {
85
64
  lastUser = newInstance(user);
86
- }
87
- };
88
- EventsUI.onClick(`.btn-account`, async (e) => {
89
- e.preventDefault();
90
- await submit();
91
- });
92
- EventsUI.onClick(`.btn-account-update-username`, async (e) => {
93
- e.preventDefault();
94
- await submit();
95
- });
96
-
97
- if (s(`.btn-confirm-email`))
98
- EventsUI.onClick(`.btn-confirm-email`, async (e) => {
99
- e.preventDefault();
100
- const result = await UserService.post({
101
- id: 'mailer/verify-email',
102
- body: { email: s(`.account-email`).value },
103
- });
65
+ const { successKeys } = await validators();
66
+ if (successKeys.length === 0) return;
67
+ const body = {};
68
+ for (const inputData of formData) {
69
+ if (!s(`.${inputData.id}`).value || s(`.${inputData.id}`).value === 'undefined') continue;
70
+ if ('model' in inputData && successKeys.includes(inputData.id)) {
71
+ body[inputData.model] = s(`.${inputData.id}`).value;
72
+ user[inputData.model] = s(`.${inputData.id}`).value;
73
+ }
74
+ }
75
+ const result = await UserService.put({ id: user._id, body });
104
76
  NotificationManager.Push({
105
- html: result.status === 'error' ? result.message : Translate.Render(`email send`),
77
+ html:
78
+ result.status === 'error' && result.message
79
+ ? result.message
80
+ : Translate.Render(`${result.status}-update-user`),
106
81
  status: result.status,
107
82
  });
83
+ if (result.status === 'success') {
84
+ user = result.data;
85
+ this.triggerUpdateEvent({ user });
86
+ if (lastUser.emailConfirmed !== user.emailConfirmed) {
87
+ this.renderVerifyEmailStatus(user);
88
+ }
89
+ lastUser = newInstance(user);
90
+ }
91
+ };
92
+ EventsUI.onClick(`.btn-account`, async (e) => {
93
+ e.preventDefault();
94
+ await submit();
108
95
  });
109
- this.renderVerifyEmailStatus(user);
110
-
111
- s(`.${waveAnimationId}`).style.cursor = 'pointer';
112
- s(`.${waveAnimationId}`).onclick = async (e) => {
113
- e.preventDefault();
114
- s(`.account-profile-image-input`).click();
115
- };
116
- EventsUI.onChange(
117
- `.account-profile-image-input`,
118
- async (e) => {
96
+ EventsUI.onClick(`.btn-account-update-username`, async (e) => {
119
97
  e.preventDefault();
120
- s(`.account-profile-image`).style.opacity = 0;
121
- const formFile = fileFormDataFactory(e, profileFileAccept);
98
+ await submit();
99
+ });
122
100
 
123
- const { status, data } = await UserService.put({
124
- id: `profile-image/${user._id}`,
125
- body: formFile,
126
- headerId: 'file',
101
+ if (s(`.btn-confirm-email`))
102
+ EventsUI.onClick(`.btn-confirm-email`, async (e) => {
103
+ e.preventDefault();
104
+ const result = await UserService.post({
105
+ id: 'mailer/verify-email',
106
+ body: { email: s(`.account-email`).value },
107
+ });
108
+ NotificationManager.Push({
109
+ html: result.status === 'error' ? result.message : Translate.Render(`email send`),
110
+ status: result.status,
111
+ });
127
112
  });
113
+ this.renderVerifyEmailStatus(user);
128
114
 
129
- if (status === 'success') {
130
- user.profileImageId = data.profileImageId;
131
- delete LogIn.Scope.user.main.model.user.profileImage;
132
- await LogIn.Trigger({ user });
133
- s(`.account-profile-image`).src = LogIn.Scope.user.main.model.user.profileImage.imageSrc;
134
- } else {
135
- NotificationManager.Push({
136
- html: Translate.Render('file-upload-failed'),
137
- status: 'error',
115
+ s(`.${waveAnimationId}`).style.cursor = 'pointer';
116
+ s(`.${waveAnimationId}`).onclick = async (e) => {
117
+ e.preventDefault();
118
+ s(`.account-profile-image-input`).click();
119
+ };
120
+ EventsUI.onChange(
121
+ `.account-profile-image-input`,
122
+ async (e) => {
123
+ e.preventDefault();
124
+ s(`.account-profile-image`).style.opacity = 0;
125
+ const formFile = fileFormDataFactory(e, profileFileAccept);
126
+
127
+ const { status, data } = await UserService.put({
128
+ id: `profile-image/${user._id}`,
129
+ body: formFile,
130
+ headerId: 'file',
138
131
  });
139
- }
140
132
 
141
- s(`.account-profile-image`).style.opacity = 1;
142
- },
143
- { loadingContainer: `.account-profile-image-loading` },
144
- );
145
- s(`.btn-account-change-password`).onclick = (e) => {
146
- e.preventDefault();
147
- // s(`.btn-close-modal-account`).click();
148
- s(`.main-btn-recover`).click();
149
- };
150
- s(`.btn-account-delete-confirm`).onclick = async (e) => {
151
- e.preventDefault();
152
- const confirmResult = await Modal.RenderConfirm({
153
- html: async () => {
154
- return html`
155
- <div class="in section-mp" style="text-align: center">${Translate.Render('confirm-delete-account')}</div>
156
- `;
133
+ if (status === 'success') {
134
+ user.profileImageId = data.profileImageId;
135
+ delete LogIn.Scope.user.main.model.user.profileImage;
136
+ await LogIn.Trigger({ user });
137
+ s(`.account-profile-image`).src = LogIn.Scope.user.main.model.user.profileImage.imageSrc;
138
+ } else {
139
+ NotificationManager.Push({
140
+ html: Translate.Render('file-upload-failed'),
141
+ status: 'error',
142
+ });
143
+ }
144
+
145
+ s(`.account-profile-image`).style.opacity = 1;
157
146
  },
158
- id: 'delete-account-modal',
147
+ { loadingContainer: `.account-profile-image-loading` },
148
+ );
149
+ s(`.btn-account-change-password`).onclick = (e) => {
150
+ e.preventDefault();
151
+ // s(`.btn-close-modal-account`).click();
152
+ s(`.main-btn-recover`).click();
153
+ };
154
+ s(`.btn-account-delete-confirm`).onclick = async (e) => {
155
+ e.preventDefault();
156
+ const confirmResult = await Modal.RenderConfirm({
157
+ html: async () => {
158
+ return html`
159
+ <div class="in section-mp" style="text-align: center">
160
+ ${Translate.Render('confirm-delete-account')}
161
+ </div>
162
+ `;
163
+ },
164
+ id: 'delete-account-modal',
165
+ });
166
+ if (confirmResult.status === 'cancelled') return;
167
+ s(`.btn-account-delete-confirm`).classList.add('hide');
168
+ s(`.btn-account-delete`).classList.remove('hide');
169
+ s(`.btn-account-delete`).click();
170
+ };
171
+ EventsUI.onClick(`.btn-account-delete`, async (e) => {
172
+ e.preventDefault();
173
+ const result = await UserService.delete({ id: user._id });
174
+ NotificationManager.Push({
175
+ html: result.status === 'error' ? result.message : Translate.Render(`success-delete-account`),
176
+ status: result.status,
177
+ });
178
+ s(`.btn-account-delete-confirm`).classList.remove('hide');
179
+ s(`.btn-account-delete`).classList.add('hide');
180
+ if (result.status === 'success') {
181
+ s(`.main-btn-home`).click();
182
+ await Auth.sessionOut();
183
+ }
159
184
  });
160
- if (confirmResult.status === 'cancelled') return;
161
- s(`.btn-account-delete-confirm`).classList.add('hide');
162
- s(`.btn-account-delete`).classList.remove('hide');
163
- s(`.btn-account-delete`).click();
164
185
  };
165
- EventsUI.onClick(`.btn-account-delete`, async (e) => {
166
- e.preventDefault();
167
- const result = await UserService.delete({ id: user._id });
168
- NotificationManager.Push({
169
- html: result.status === 'error' ? result.message : Translate.Render(`success-delete-account`),
170
- status: result.status,
171
- });
172
- s(`.btn-account-delete-confirm`).classList.remove('hide');
173
- s(`.btn-account-delete`).classList.add('hide');
174
- if (result.status === 'success') {
175
- LogOut.Trigger();
176
- s(`.main-btn-home`).click();
177
- }
178
- });
186
+ await this.instanceModalUiEvents({ user });
179
187
  });
180
188
  return html`
181
189
  <input type="file" accept="${profileFileAccept.join(', ')}" class="account-profile-image-input hide" />
@@ -285,6 +293,16 @@ const Account = {
285
293
  if (user.emailConfirmed === true) s(`.account-email`).setAttribute('disabled', '');
286
294
  }
287
295
  },
296
+ instanceModalUiEvents: async (user) => null,
297
+ updateForm: async function (user) {
298
+ if (!s(`.modal-account`)) return;
299
+ await this.instanceModalUiEvents({ user });
300
+ s(`.account-profile-image`).style.opacity = 0;
301
+ for (const inputData of this.formData)
302
+ if (s(`.${inputData.id}`)) s(`.${inputData.id}`).value = user[inputData.model];
303
+ s(`.account-profile-image`).src = LogIn.Scope.user.main.model.user.profileImage.imageSrc;
304
+ s(`.account-profile-image`).style.opacity = 1;
305
+ },
288
306
  };
289
307
 
290
308
  export { Account };
@@ -1,7 +1,19 @@
1
+ import { UserMock, UserService } from '../../services/user/user.service.js';
2
+ import { Account } from './Account.js';
3
+ import { loggerFactory } from './Logger.js';
4
+ import { LogIn } from './LogIn.js';
5
+ import { LogOut } from './LogOut.js';
6
+ import { SignUp } from './SignUp.js';
7
+
8
+ const logger = loggerFactory(import.meta);
9
+
1
10
  const token = Symbol('token');
2
11
 
12
+ const guestToken = Symbol('guestToken');
13
+
3
14
  const Auth = {
4
15
  [token]: '',
16
+ [guestToken]: '',
5
17
  setToken: function (value = '') {
6
18
  return (this[token] = value);
7
19
  },
@@ -11,8 +23,89 @@ const Auth = {
11
23
  getToken: function () {
12
24
  return this[token];
13
25
  },
26
+ setGuestToken: function (value = '') {
27
+ return (this[guestToken] = value);
28
+ },
29
+ deleteGuestToken: function () {
30
+ return (this[guestToken] = '');
31
+ },
32
+ getGuestToken: function () {
33
+ return this[guestToken];
34
+ },
35
+ // jwt
14
36
  getJWT: function () {
15
- return `Bearer ${this.getToken()}`;
37
+ return `Bearer ${this.getToken() ? this.getToken() : this.getGuestToken()}`;
38
+ },
39
+ signUpToken: async function (
40
+ result = {
41
+ data: {
42
+ token: '',
43
+ user: null,
44
+ },
45
+ },
46
+ ) {
47
+ try {
48
+ localStorage.setItem('jwt', result.data.token);
49
+ await Auth.sessionIn();
50
+ await SignUp.Trigger(result.data);
51
+ } catch (error) {
52
+ logger.error(error);
53
+ localStorage.removeItem('jwt');
54
+ }
55
+ },
56
+ sessionIn: async function (userServicePayload) {
57
+ try {
58
+ const token = userServicePayload?.data?.token ? userServicePayload.data.token : localStorage.getItem('jwt');
59
+
60
+ if (token) {
61
+ this.setToken(token);
62
+ const result = userServicePayload
63
+ ? userServicePayload
64
+ : await (async () => {
65
+ const _result = await UserService.get({ id: 'auth' });
66
+ return {
67
+ status: _result.status,
68
+ data: {
69
+ user: _result.data,
70
+ },
71
+ };
72
+ })();
73
+ const { status, data, message } = result;
74
+ if (status === 'success') {
75
+ localStorage.setItem('jwt', token);
76
+ await LogIn.Trigger({ user: data.user });
77
+ await Account.updateForm(data.user);
78
+ return { user: data.user };
79
+ } else throw new Error(message);
80
+ }
81
+
82
+ // anon guest session
83
+ this.deleteToken();
84
+ localStorage.removeItem('jwt');
85
+ let guestToken = localStorage.getItem('jwt.g');
86
+
87
+ if (!guestToken) {
88
+ const result = await UserService.post({ id: 'guest' });
89
+ localStorage.setItem('jwt.g', result.data.token);
90
+ guestToken = result.data.token;
91
+ }
92
+
93
+ this.setGuestToken(guestToken);
94
+ let { data, status, message } = await UserService.get({ id: 'auth' });
95
+ if (status === 'error') throw new Error(message);
96
+ await Account.updateForm(data);
97
+ return { user: data };
98
+ } catch (error) {
99
+ logger.error(error);
100
+ localStorage.removeItem('jwt');
101
+ localStorage.removeItem('jwt.g');
102
+ return { user: UserMock.default };
103
+ }
104
+ },
105
+ sessionOut: async function () {
106
+ this.deleteToken();
107
+ localStorage.removeItem('jwt');
108
+ await LogOut.Trigger(await this.sessionIn());
16
109
  },
17
110
  };
18
111
 
@@ -344,7 +344,7 @@ const renderBubbleDialog = async function (
344
344
  }
345
345
  if (options.trianglePositionCss) cssTrianglePosition = options.trianglePositionCss;
346
346
  return html` <div
347
- class="${options?.classSelectors ? options.classSelectors : 'inl'} bubble-dialog bubble-dialog-${id}"
347
+ class="${options?.classSelectors ? options.classSelectors : 'in'} bubble-dialog bubble-dialog-${id}"
348
348
  ${options.bubbleCss ? `style='${options.bubbleCss}'` : ''}
349
349
  >
350
350
  <style class="style-bubble-dialog-triangle-${id}">
@@ -79,7 +79,7 @@ const Docs = {
79
79
  icon: html`<i class="fa-brands fa-osi"></i>`,
80
80
  text: 'Source Docs',
81
81
  url: function () {
82
- return `${getProxyPath()}docs/engine/2.7.94`;
82
+ return `${getProxyPath()}docs/engine/2.8.0`;
83
83
  },
84
84
  },
85
85
  {
@@ -1,6 +1,7 @@
1
1
  import { CoreService } from '../../services/core/core.service.js';
2
2
  import { FileService } from '../../services/file/file.service.js';
3
3
  import { UserService } from '../../services/user/user.service.js';
4
+ import { Auth } from './Auth.js';
4
5
  import { BtnIcon } from './BtnIcon.js';
5
6
  import { EventsUI } from './EventsUI.js';
6
7
  import { Input } from './Input.js';
@@ -102,7 +103,13 @@ const LogIn = {
102
103
  if ('model' in inputData) body[inputData.model] = s(`.${inputData.id}`).value;
103
104
  }
104
105
  const result = await UserService.post({ id: 'auth', body });
105
- if (result.status === 'success') this.Trigger(result.data);
106
+ if (result.status === 'success') {
107
+ await Auth.sessionIn(result);
108
+ setTimeout(() => {
109
+ if (s(`.modal-log-in`)) s(`.btn-close-modal-log-in`).click();
110
+ if (s(`.modal-sign-up`)) s(`.btn-close-modal-sign-up`).click();
111
+ });
112
+ }
106
113
  if (result.status === 'error' && result.message.match('attempts')) {
107
114
  htmls(`.login-attempt-warn-value`, result.message.split(':')[1]);
108
115
  s(`.login-attempt-warn-container`).classList.remove('hide');
@@ -1,3 +1,4 @@
1
+ import { Auth } from './Auth.js';
1
2
  import { BtnIcon } from './BtnIcon.js';
2
3
  import { LogIn } from './LogIn.js';
3
4
  import { Translate } from './Translate.js';
@@ -37,9 +38,9 @@ const LogOut = {
37
38
  },
38
39
  Render: async function () {
39
40
  setTimeout(() => {
40
- s('.btn-log-out').onclick = (e) => {
41
+ s('.btn-log-out').onclick = async (e) => {
41
42
  e.preventDefault();
42
- LogOut.Trigger();
43
+ await Auth.sessionOut();
43
44
  };
44
45
  });
45
46
  // Translate.Render('confirm-logout')
@@ -87,6 +87,8 @@ const Modal = {
87
87
  onObserverListener: {},
88
88
  onClickListener: {},
89
89
  onExpandUiListener: {},
90
+ onBarUiOpen: {},
91
+ onBarUiClose: {},
90
92
  query: options.query ? `${window.location.search}` : undefined,
91
93
  };
92
94
  if (options && 'mode' in options) {
@@ -286,6 +288,8 @@ const Modal = {
286
288
  s(`.modal-menu`).style.top = '0px';
287
289
  s(`.main-body-btn-container`).style.top = '50px';
288
290
  s(`.main-body`).style.top = '0px';
291
+ for (const event of Object.keys(Modal.Data[idModal].onBarUiClose))
292
+ Modal.Data[idModal].onBarUiClose[event]();
289
293
  } else {
290
294
  s(`.main-body-btn-ui-close`).classList.remove('hide');
291
295
  s(`.main-body-btn-ui-open`).classList.add('hide');
@@ -296,6 +300,8 @@ const Modal = {
296
300
  s(`.slide-menu-top-bar`).classList.remove('hide');
297
301
  s(`.bottom-bar`).classList.remove('hide');
298
302
  s(`.main-body`).style.top = `${options.heightTopBar}px`;
303
+ for (const event of Object.keys(Modal.Data[idModal].onBarUiOpen))
304
+ Modal.Data[idModal].onBarUiOpen[event]();
299
305
  }
300
306
  Responsive.Event[`slide-menu-modal-menu`]();
301
307
  Object.keys(this.Data).map((_idModal) => {
@@ -1321,7 +1327,6 @@ const Modal = {
1321
1327
  case 'slide-menu-right':
1322
1328
  case 'slide-menu-left':
1323
1329
  const backMenuButtonEvent = async () => {
1324
- if (location.pathname !== getProxyPath()) setPath(getProxyPath());
1325
1330
  if (s(`.menu-btn-container-children`)) htmls(`.menu-btn-container-children`, '');
1326
1331
  // htmls(`.nav-title-display-${'modal-menu'}`, html`<i class="fas fa-home"></i> ${Translate.Render('home')}`);
1327
1332
  htmls(`.nav-title-display-${'modal-menu'}`, html``);
@@ -1340,8 +1345,7 @@ const Modal = {
1340
1345
  backMenuButtonEvent();
1341
1346
  }
1342
1347
  s(`.btn-close-modal-menu`).click();
1343
- if (window.location.pathname !== (getProxyPath() === '/' ? getProxyPath() : `${getProxyPath()}/`))
1344
- setPath(getProxyPath());
1348
+ setPath(getProxyPath());
1345
1349
  setDocTitle({ ...options.RouterInstance, route: '' });
1346
1350
  };
1347
1351
  EventsUI.onClick(`.btn-icon-menu-back`, backMenuButtonEvent);
@@ -372,13 +372,26 @@ const Panel = {
372
372
  } else {
373
373
  Responsive.Event[`${idPanel}-responsive`] = () => {
374
374
  if (s(`.${idPanel}-form-container`))
375
- s(`.${idPanel}-form-container`).style.maxHeight = `${
376
- window.innerHeight - heightTopBar - heightBottomBar
377
- }px`;
375
+ s(`.${idPanel}-form-container`).style.maxHeight =
376
+ options.route === 'home' &&
377
+ s(`.${idPanel}-form-body`) &&
378
+ !s(`.${idPanel}-form-body`).classList.contains('hide') &&
379
+ !s(`.main-body-btn-ui-open`).classList.contains('hide')
380
+ ? `${window.innerHeight}px`
381
+ : `${window.innerHeight - heightTopBar - heightBottomBar}px`;
378
382
  };
379
383
  Responsive.Event[`${idPanel}-responsive`]();
380
384
  }
381
385
  };
386
+ if (options.route === 'home') {
387
+ Modal.Data['modal-menu'].onBarUiClose[`${idPanel}-responsive`] = () => {
388
+ resizeParentModal();
389
+ };
390
+
391
+ Modal.Data['modal-menu'].onBarUiOpen[`${idPanel}-responsive`] = () => {
392
+ resizeParentModal();
393
+ };
394
+ }
382
395
  setTimeout(resizeParentModal);
383
396
  if (options.route) {
384
397
  RouterEvents[options.parentIdModal] = ({ route }) => {
@@ -113,6 +113,7 @@ const PanelForm = {
113
113
  filesData: () => PanelForm.Data[idPanel].filesData,
114
114
  scrollClassContainer: options.scrollClassContainer ? options.scrollClassContainer : 'main-body',
115
115
  titleIcon,
116
+ route: options.route,
116
117
  formContainerClass: 'session-in-log-in',
117
118
  onClick: async function ({ payload }) {
118
119
  if (options.route) {
@@ -1,8 +1,8 @@
1
1
  import { UserService } from '../../services/user/user.service.js';
2
+ import { Auth } from './Auth.js';
2
3
  import { BtnIcon } from './BtnIcon.js';
3
4
  import { EventsUI } from './EventsUI.js';
4
5
  import { Input } from './Input.js';
5
- import { LogIn } from './LogIn.js';
6
6
  import { NotificationManager } from './NotificationManager.js';
7
7
  import { Translate } from './Translate.js';
8
8
  import { Validator } from './Validator.js';
@@ -52,10 +52,7 @@ const SignUp = {
52
52
  : Translate.Render(`no-valid-register`),
53
53
  status: result.status,
54
54
  });
55
- if (result.status === 'success') {
56
- await this.Trigger(result.data);
57
- await LogIn.Trigger(result.data);
58
- }
55
+ if (result.status === 'success') await Auth.signUpToken(result);
59
56
  });
60
57
  s(`.btn-sign-up-i-have-account`).onclick = () => {
61
58
  s(`.main-btn-log-in`).click();
@@ -35,6 +35,8 @@ const SocketIo = {
35
35
  // forceNew: true,
36
36
  // reconnectionAttempts: 'Infinity',
37
37
  // timeout: 10000,
38
+ // withCredentials: true,
39
+ // autoConnect: 5000,
38
40
  transports: ['websocket', 'polling', 'flashsocket'],
39
41
  };
40
42
  // logger.error(`connect options:`, JSON.stringify(connectOptions, null, 4));
@@ -88,11 +88,18 @@ const Validator = {
88
88
 
89
89
  return async () => {
90
90
  let errorMessage = '';
91
+ const errorKeys = [];
92
+ const successKeys = [];
91
93
  for (const validatorKey of Object.keys(validatorFunction)) {
92
94
  const result = await validatorFunction[validatorKey]();
93
- if (result && result.errorMessage) errorMessage += result.errorMessage;
95
+ if (result && result.errorMessage) {
96
+ errorMessage += result.errorMessage;
97
+ errorKeys.push(validatorKey);
98
+ } else {
99
+ successKeys.push(validatorKey);
100
+ }
94
101
  }
95
- return { errorMessage };
102
+ return { errorMessage, errorKeys, successKeys };
96
103
  };
97
104
  },
98
105
  };
@@ -148,7 +148,10 @@ const pasteData = () => new Promise((resolve) => navigator.clipboard.readText().
148
148
  * history.
149
149
  * @memberof VanillaJS
150
150
  */
151
- const setPath = (path = '/', stateStorage = {}, title = '') => history.pushState(stateStorage, title, path);
151
+ const setPath = (path = '/', stateStorage = {}, title = '') => {
152
+ if (window.location.pathname === path || window.location.pathname === `${path}/`) return;
153
+ return history.pushState(stateStorage, title, path);
154
+ };
152
155
 
153
156
  /**
154
157
  * The function `getQueryParams` extracts query parameters from the current URL and returns them as an
@@ -7,35 +7,14 @@ import { ElementsDefault } from './ElementsDefault.js';
7
7
  const LogInDefault = async function () {
8
8
  LogIn.Event['LogInDefault'] = async (options) => {
9
9
  const { token, user } = options;
10
-
11
- if (token) {
12
- localStorage.setItem('jwt', token);
13
- Auth.setToken(token);
14
- }
15
10
  ElementsDefault.Data.user.main.model.user = user;
16
-
17
11
  s(`.main-btn-log-in`).style.display = 'none';
18
12
  s(`.main-btn-sign-up`).style.display = 'none';
19
13
  s(`.main-btn-log-out`).style.display = null;
20
14
  s(`.main-btn-account`).style.display = null;
21
-
22
- if (s(`.modal-log-in`)) s(`.btn-close-modal-log-in`).click();
23
- if (s(`.modal-sign-up`)) s(`.btn-close-modal-sign-up`).click();
24
15
  };
25
- const token = localStorage.getItem('jwt');
26
- if (token) {
27
- Auth.setToken(token);
28
- const result = await UserService.get({ id: 'auth' });
29
- if (result.status === 'success' && result.data) {
30
- const user = result.data;
31
- await LogIn.Trigger({
32
- token,
33
- user,
34
- });
35
- } else localStorage.removeItem('jwt');
36
- } else {
37
- // Anon
38
- }
16
+ const { user } = await Auth.sessionIn();
17
+ ElementsDefault.Data.user.main.model.user = user;
39
18
  };
40
19
 
41
20
  export { LogInDefault };
@@ -6,8 +6,9 @@ import { s } from '../core/VanillaJs.js';
6
6
  import { ElementsDefault } from './ElementsDefault.js';
7
7
 
8
8
  const LogOutDefault = async function () {
9
- LogOut.Event['LogOutDefault'] = async () => {
10
- localStorage.removeItem('jwt');
9
+ LogOut.Event['LogOutDefault'] = async (result = { user: { _id: '' } }) => {
10
+ ElementsDefault.Data.user.main.model.user = result.user;
11
+
11
12
  s(`.main-btn-log-out`).style.display = 'none';
12
13
  s(`.main-btn-account`).style.display = 'none';
13
14
  s(`.main-btn-log-in`).style.display = null;
@@ -15,9 +16,6 @@ const LogOutDefault = async function () {
15
16
  if (s(`.modal-log-out`)) s(`.btn-close-modal-log-out`).click();
16
17
  if (s(`.modal-account`)) s(`.btn-close-modal-account`).click();
17
18
 
18
- ElementsDefault.Data.user.main.model.user = { _id: '' };
19
- Auth.deleteToken();
20
-
21
19
  NotificationManager.Push({
22
20
  html: Translate.Render(`success-logout`),
23
21
  status: 'success',
@@ -8,6 +8,14 @@ logger.info('Load service');
8
8
 
9
9
  const endpoint = 'user';
10
10
 
11
+ const UserMock = {
12
+ default: {
13
+ username: 'guest',
14
+ email: `guest@${location.hostname}`,
15
+ role: 'guest',
16
+ },
17
+ };
18
+
11
19
  const UserService = {
12
20
  post: (options = { id: '', body: {} }) =>
13
21
  new Promise((resolve, reject) =>
@@ -86,4 +94,4 @@ const UserService = {
86
94
  ),
87
95
  };
88
96
 
89
- export { UserService };
97
+ export { UserService, UserMock };
@@ -109,6 +109,6 @@ SrrComponent = ({ ttiLoadTimeLimit }) => {
109
109
  const CacheControl = ${CacheControl};
110
110
  CacheControl({ ttiLoadTimeLimit: ${ttiLoadTimeLimit ? ttiLoadTimeLimit : 1000 * 70 * 1} });
111
111
  </script>
112
- <div class="clean-cache-container">v2.7.94</div>
112
+ <div class="clean-cache-container">v2.8.0</div>
113
113
  `;
114
114
  };
@@ -64,10 +64,16 @@ self.addEventListener('fetch', (event) => {
64
64
  } catch (error) {
65
65
  console.error('Error opening cache for pre cached page', event.request.url, error);
66
66
  try {
67
- const cache = await caches.open(CACHE_NAME);
68
- const preCachedResponse = await cache.match(`${PROXY_PATH === '/' ? '' : PROXY_PATH}/offline/index.html`);
69
- if (!preCachedResponse) throw new Error(error.message);
70
- return preCachedResponse;
67
+ if (event.request.method.toUpperCase() === 'GET') {
68
+ const cache = await caches.open(CACHE_NAME);
69
+ const preCachedResponse = await cache.match(`${PROXY_PATH === '/' ? '' : PROXY_PATH}/offline/index.html`);
70
+ if (!preCachedResponse) throw new Error(error.message);
71
+ return preCachedResponse;
72
+ }
73
+ const response = new Response(JSON.stringify({ status: 'error', message: 'offline test response' }));
74
+ // response.status = 200;
75
+ response.headers.set('Content-Type', 'application/json');
76
+ return response;
71
77
  } catch (error) {
72
78
  console.error('Error opening cache for offline page', event.request.url, error);
73
79
  const response = new Response(JSON.stringify({ status: 'error', message: error.message }));
@@ -700,28 +700,7 @@ root file where the route starts, such as index.js, app.js, routes.js, etc ... *
700
700
 
701
701
  await swaggerAutoGen({ openapi: '3.0.0' })(outputFile, routes, doc);
702
702
  }
703
- if (!enableLiveRebuild && process.argv.includes('zip')) {
704
- logger.warn('build zip', rootClientPath);
705
-
706
- if (!fs.existsSync('./build')) fs.mkdirSync('./build');
707
-
708
- const zip = new AdmZip();
709
- const files = await fs.readdir(rootClientPath, { recursive: true });
710
-
711
- for (const relativePath of files) {
712
- const filePath = dir.resolve(`${rootClientPath}/${relativePath}`);
713
- if (!fs.lstatSync(filePath).isDirectory()) {
714
- const folder = dir.relative(`public/${host}${path}`, dir.dirname(filePath));
715
- zip.addLocalFile(filePath, folder);
716
- }
717
- }
718
-
719
- const buildId = `${host}-${path.replaceAll('/', '')}`;
720
703
 
721
- logger.warn('write zip', `./build/${buildId}.zip`);
722
-
723
- zip.writeZip(`./build/${buildId}.zip`);
724
- }
725
704
  if (client) {
726
705
  let PRE_CACHED_RESOURCES = [];
727
706
 
@@ -792,6 +771,28 @@ root file where the route starts, such as index.js, app.js, routes.js, etc ... *
792
771
  );
793
772
  }
794
773
  }
774
+ if (!enableLiveRebuild && process.argv.includes('zip')) {
775
+ logger.warn('build zip', rootClientPath);
776
+
777
+ if (!fs.existsSync('./build')) fs.mkdirSync('./build');
778
+
779
+ const zip = new AdmZip();
780
+ const files = await fs.readdir(rootClientPath, { recursive: true });
781
+
782
+ for (const relativePath of files) {
783
+ const filePath = dir.resolve(`${rootClientPath}/${relativePath}`);
784
+ if (!fs.lstatSync(filePath).isDirectory()) {
785
+ const folder = dir.relative(`public/${host}${path}`, dir.dirname(filePath));
786
+ zip.addLocalFile(filePath, folder);
787
+ }
788
+ }
789
+
790
+ const buildId = `${host}-${path.replaceAll('/', '')}`;
791
+
792
+ logger.warn('write zip', `./build/${buildId}.zip`);
793
+
794
+ zip.writeZip(`./build/${buildId}.zip`);
795
+ }
795
796
  }
796
797
  }
797
798
  };
@@ -3,8 +3,9 @@ import { favicons } from 'favicons';
3
3
  // import textToImage from 'text-to-image';
4
4
  import { loggerFactory } from './logger.js';
5
5
  import fs from 'fs-extra';
6
- import { png3x } from 'font-awesome-assets';
7
6
  import { getCapVariableName, s4 } from '../client/components/core/CommonJs.js';
7
+ import { FileFactory } from '../api/file/file.service.js';
8
+ import { svg, png, png3x } from 'font-awesome-assets';
8
9
 
9
10
  const logger = loggerFactory(import.meta);
10
11
 
@@ -148,4 +149,13 @@ const buildIcons = async ({
148
149
  }
149
150
  };
150
151
 
151
- export { buildIcons, buildTextImg, defaultBaseTextImgOptions, faBase64Png, getBufferPngText };
152
+ const getDefaultProfileImageId = async (File) => {
153
+ const faId = 'user';
154
+ const tmpFilePath = `./tmp/${faId}-${s4() + s4()}.svg`;
155
+ fs.writeFileSync(tmpFilePath, svg(faId, '#f5f5f5d1'), 'utf8');
156
+ const file = await new File(FileFactory.svg(fs.readFileSync(tmpFilePath), `${faId}.svg`)).save();
157
+ fs.removeSync(tmpFilePath);
158
+ return file._id;
159
+ };
160
+
161
+ export { buildIcons, buildTextImg, defaultBaseTextImgOptions, faBase64Png, getBufferPngText, getDefaultProfileImageId };
@@ -583,7 +583,7 @@ const validateTemplatePath = (absolutePath = '') => {
583
583
  const confServer = DefaultConf.server[host][path];
584
584
  const confClient = DefaultConf.client[client];
585
585
  const confSsr = DefaultConf.ssr[ssr];
586
- const clients = Object.keys(confClient).concat(['core', 'test', 'default']);
586
+ const clients = Object.keys(confClient).concat(['core', 'test', 'default', 'user']);
587
587
 
588
588
  if (absolutePath.match('src/api') && !confServer.apis.find((p) => absolutePath.match(`src/api/${p}/`))) {
589
589
  return false;
@@ -935,6 +935,7 @@ const maintenanceMiddleware = (req, res, port, proxyRouter) => {
935
935
 
936
936
  const setUpProxyMaintenanceServer = ({ deployGroupId }) => {
937
937
  shellExec(`pm2 kill`);
938
+ shellExec(`node bin/deploy valkey-service`);
938
939
  const proxyDeployId = fs.readFileSync(`./engine-private/deploy/${deployGroupId}.proxy`, 'utf8').trim();
939
940
  shellExec(`node bin/deploy conf ${proxyDeployId} production`);
940
941
  shellExec(`node bin/deploy run ${proxyDeployId} maintenance`);
@@ -74,6 +74,10 @@ const saveRuntimeRouter = async () => {
74
74
  const host = process.env.DEFAULT_DEPLOY_HOST;
75
75
  const path = process.env.DEFAULT_DEPLOY_PATH;
76
76
  const confServerPath = `./engine-private/conf/${deployId}/conf.server.json`;
77
+ if (!deployId || !host || !path) {
78
+ logger.warn('default deploy instance not found');
79
+ return;
80
+ }
77
81
  const confServer = JSON.parse(fs.readFileSync(confServerPath, 'utf8'));
78
82
  const { db } = confServer[host][path];
79
83
 
package/src/server/ssl.js CHANGED
@@ -50,8 +50,8 @@ const buildSSL = async (host) => {
50
50
 
51
51
  fs.writeFileSync(`./engine-private/ssl/${host}/_ca_bundle.crt`, ca, 'utf8');
52
52
  fs.writeFileSync(`./engine-private/ssl/${host}/_ca_full_bundle.crt`, caFull, 'utf8');
53
-
54
- fs.removeSync(`${sslPath}/${folderHost}`);
53
+ // TODO: no repeat folderHost match
54
+ // fs.removeSync(`${sslPath}/${folderHost}`);
55
55
  return true;
56
56
  }
57
57
  }
@@ -0,0 +1,125 @@
1
+ import Valkey from 'iovalkey';
2
+ import mongoose from 'mongoose';
3
+ import { hashPassword } from './auth.js';
4
+ import { loggerFactory } from './logger.js';
5
+
6
+ const logger = loggerFactory(import.meta);
7
+
8
+ let valkeyEnabled = true;
9
+
10
+ const disableValkeyErrorMessage = 'valkey is not enabled';
11
+
12
+ const isValkeyEnable = () => valkeyEnabled;
13
+
14
+ const selectDtoFactory = (payload, select) => {
15
+ const result = {};
16
+ for (const key of Object.keys(select)) {
17
+ if (select[key] === 1 && key in payload) result[key] = payload[key];
18
+ }
19
+ return result;
20
+ };
21
+
22
+ const valkeyClientFactory = async () => {
23
+ const valkey = new Valkey({
24
+ retryStrategy: (attempt) => {
25
+ if (attempt === 1) {
26
+ valkey.disconnect();
27
+ valkeyEnabled = false;
28
+ logger.warn('Valkey service not enabled', { valkeyEnabled });
29
+ return;
30
+ }
31
+ return 1000; // 1 second interval attempt
32
+ },
33
+ }); // Connect to 127.0.0.1:6379
34
+ // new Valkey(6380); // 127.0.0.1:6380
35
+ // new Valkey(6379, '192.168.1.1'); // 192.168.1.1:6379
36
+ // new Valkey('/tmp/redis.sock');
37
+ // new Valkey({
38
+ // port: 6379, // Valkey port
39
+ // host: '127.0.0.1', // Valkey host
40
+ // username: 'default', // needs Valkey >= 6
41
+ // password: 'my-top-secret',
42
+ // db: 0, // Defaults to 0
43
+ // });
44
+ return valkey;
45
+ };
46
+
47
+ const getValkeyObject = async (key = '') => {
48
+ if (!valkeyEnabled) {
49
+ logger.warn(disableValkeyErrorMessage + ' get', key);
50
+ return null;
51
+ }
52
+ const object = await valkey.get(key);
53
+ try {
54
+ return JSON.parse(object);
55
+ } catch (error) {
56
+ return object;
57
+ }
58
+ };
59
+
60
+ const setValkeyObject = async (key = '', payload = {}) => {
61
+ if (!valkeyEnabled) throw new Error(disableValkeyErrorMessage);
62
+ return await valkey.set(key, JSON.stringify(payload));
63
+ };
64
+
65
+ const updateValkeyObject = async (key = '', payload = {}) => {
66
+ if (!valkeyEnabled) throw new Error(disableValkeyErrorMessage);
67
+ const object = await getValkeyObject(key, valkey);
68
+ object.updatedAt = new Date().toISOString();
69
+ return await valkey.set(key, JSON.stringify({ ...object, ...payload }));
70
+ };
71
+
72
+ const valkeyObjectFactory = async (module = '', options = { host: 'localhost', object: {} }) => {
73
+ if (!valkeyEnabled) throw new Error(disableValkeyErrorMessage);
74
+ const idoDate = new Date().toISOString();
75
+ options.object = options.object || {};
76
+ const { object } = options;
77
+ const _id = new mongoose.Types.ObjectId().toString();
78
+ object._id = _id;
79
+ object.createdAt = idoDate;
80
+ object.updatedAt = idoDate;
81
+ switch (module) {
82
+ case 'user': {
83
+ const role = 'guest';
84
+ object._id = `${role}${_id}`;
85
+ return {
86
+ ...object,
87
+ username: `${role}${_id.slice(-5)}`,
88
+ email: `${_id}@${options.host}`,
89
+ password: hashPassword(process.env.JWT_SECRET),
90
+ role,
91
+ failedLoginAttempts: 0,
92
+ phoneNumbers: [],
93
+ publicKey: [],
94
+ profileImageId: null,
95
+ emailConfirmed: false,
96
+ recoverTimeOut: null,
97
+ lastLoginDate: null,
98
+ };
99
+ }
100
+ default:
101
+ throw new Error(`module schema not found: ${module}`);
102
+ }
103
+ };
104
+
105
+ const ValkeyAPI = {
106
+ valkeyClientFactory,
107
+ selectDtoFactory,
108
+ getValkeyObject,
109
+ setValkeyObject,
110
+ valkeyObjectFactory,
111
+ updateValkeyObject,
112
+ };
113
+
114
+ const valkey = await ValkeyAPI.valkeyClientFactory();
115
+
116
+ export {
117
+ valkeyClientFactory,
118
+ selectDtoFactory,
119
+ getValkeyObject,
120
+ setValkeyObject,
121
+ valkeyObjectFactory,
122
+ updateValkeyObject,
123
+ isValkeyEnable,
124
+ ValkeyAPI,
125
+ };