underpost 3.2.5 → 3.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/.github/workflows/release.cd.yml +1 -2
- package/CHANGELOG.md +251 -1
- package/CLI-HELP.md +26 -13
- package/Dockerfile +0 -4
- package/README.md +3 -3
- package/bin/build.js +13 -3
- package/bin/deploy.js +570 -1
- package/bin/file.js +5 -0
- package/conf.js +11 -2
- package/jsconfig.json +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -6
- package/manifests/deployment/dd-test-development/deployment.yaml +136 -66
- package/manifests/deployment/dd-test-development/proxy.yaml +41 -5
- package/package.json +20 -11
- package/src/api/core/core.controller.js +10 -10
- package/src/api/core/core.service.js +10 -10
- package/src/api/default/default.controller.js +10 -10
- package/src/api/default/default.service.js +10 -10
- package/src/api/document/document.controller.js +12 -12
- package/src/api/document/document.model.js +10 -16
- package/src/api/file/file.controller.js +8 -8
- package/src/api/file/file.model.js +10 -10
- package/src/api/file/file.service.js +36 -36
- package/src/api/test/test.controller.js +8 -8
- package/src/api/test/test.service.js +8 -8
- package/src/api/user/guest.service.js +99 -0
- package/src/api/user/user.controller.js +6 -6
- package/src/api/user/user.model.js +8 -13
- package/src/api/user/user.service.js +3 -20
- package/src/cli/deploy.js +33 -30
- package/src/cli/fs.js +62 -5
- package/src/cli/image.js +43 -1
- package/src/cli/index.js +5 -1
- package/src/cli/release.js +57 -1
- package/src/cli/repository.js +35 -3
- package/src/cli/run.js +300 -35
- package/src/cli/ssh.js +1 -1
- package/src/cli/static.js +43 -115
- package/src/client/Default.index.js +21 -33
- package/src/client/components/core/404.js +4 -4
- package/src/client/components/core/500.js +4 -4
- package/src/client/components/core/Account.js +73 -60
- package/src/client/components/core/AgGrid.js +23 -33
- package/src/client/components/core/Alert.js +12 -13
- package/src/client/components/core/AppStore.js +1 -1
- package/src/client/components/core/Auth.js +20 -32
- package/src/client/components/core/Badge.js +7 -13
- package/src/client/components/core/BtnIcon.js +15 -17
- package/src/client/components/core/CalendarCore.js +42 -63
- package/src/client/components/core/Chat.js +13 -15
- package/src/client/components/core/ClientEvents.js +87 -0
- package/src/client/components/core/ColorPaletteElement.js +309 -0
- package/src/client/components/core/Content.js +17 -14
- package/src/client/components/core/Css.js +15 -71
- package/src/client/components/core/CssCore.js +12 -16
- package/src/client/components/core/D3Chart.js +4 -4
- package/src/client/components/core/Docs.js +60 -59
- package/src/client/components/core/DropDown.js +69 -91
- package/src/client/components/core/EventBus.js +92 -0
- package/src/client/components/core/EventsUI.js +14 -17
- package/src/client/components/core/FileExplorer.js +102 -234
- package/src/client/components/core/FullScreen.js +47 -75
- package/src/client/components/core/Input.js +24 -69
- package/src/client/components/core/Keyboard.js +25 -18
- package/src/client/components/core/KeyboardAvoidance.js +145 -0
- package/src/client/components/core/LoadingAnimation.js +25 -31
- package/src/client/components/core/LogIn.js +41 -41
- package/src/client/components/core/LogOut.js +23 -14
- package/src/client/components/core/Modal.js +397 -176
- package/src/client/components/core/NotificationManager.js +14 -18
- package/src/client/components/core/Panel.js +54 -50
- package/src/client/components/core/PanelForm.js +25 -125
- package/src/client/components/core/Polyhedron.js +110 -214
- package/src/client/components/core/PublicProfile.js +39 -32
- package/src/client/components/core/Recover.js +52 -48
- package/src/client/components/core/Responsive.js +88 -32
- package/src/client/components/core/RichText.js +9 -18
- package/src/client/components/core/Router.js +24 -3
- package/src/client/components/core/SearchBox.js +37 -37
- package/src/client/components/core/SignUp.js +39 -30
- package/src/client/components/core/SocketIo.js +31 -2
- package/src/client/components/core/SocketIoHandler.js +6 -6
- package/src/client/components/core/ToggleSwitch.js +8 -20
- package/src/client/components/core/ToolTip.js +5 -17
- package/src/client/components/core/Translate.js +56 -59
- package/src/client/components/core/Validator.js +26 -16
- package/src/client/components/core/Wallet.js +15 -26
- package/src/client/components/core/Worker.js +140 -25
- package/src/client/components/core/windowGetDimensions.js +7 -7
- package/src/client/components/default/{MenuDefault.js → AppShellDefault.js} +87 -87
- package/src/client/components/default/CssDefault.js +12 -12
- package/src/client/components/default/LogInDefault.js +6 -4
- package/src/client/components/default/LogOutDefault.js +6 -4
- package/src/client/components/default/RouterDefault.js +47 -0
- package/src/client/components/default/SettingsDefault.js +4 -4
- package/src/client/components/default/SignUpDefault.js +6 -4
- package/src/client/components/default/TranslateDefault.js +3 -3
- package/src/client/services/core/core.service.js +17 -49
- package/src/client/services/default/default.management.js +139 -242
- package/src/client/services/default/default.service.js +10 -16
- package/src/client/services/document/document.service.js +14 -19
- package/src/client/services/file/file.service.js +8 -13
- package/src/client/services/test/test.service.js +8 -13
- package/src/client/services/user/guest.service.js +79 -0
- package/src/client/services/user/user.management.js +5 -5
- package/src/client/services/user/user.service.js +14 -20
- package/src/client/ssr/body/404.js +3 -3
- package/src/client/ssr/body/500.js +3 -3
- package/src/client/ssr/body/CacheControl.js +5 -2
- package/src/client/ssr/body/DefaultSplashScreen.js +19 -12
- package/src/client/ssr/mailer/DefaultRecoverEmail.js +19 -20
- package/src/client/ssr/mailer/DefaultVerifyEmail.js +15 -16
- package/src/client/ssr/offline/Maintenance.js +12 -11
- package/src/client/ssr/offline/NoNetworkConnection.js +3 -3
- package/src/client/ssr/pages/Test.js +2 -2
- package/src/client/sw/core.sw.js +212 -0
- package/src/index.js +1 -1
- package/src/runtime/express/Dockerfile +4 -4
- package/src/runtime/lampp/Dockerfile +8 -7
- package/src/runtime/wp/Dockerfile +11 -17
- package/src/server/client-build-docs.js +45 -46
- package/src/server/client-build.js +334 -60
- package/src/server/client-formatted.js +47 -16
- package/src/server/conf.js +5 -4
- package/src/server/ipfs-client.js +232 -91
- package/src/server/process.js +13 -27
- package/src/server/start.js +6 -3
- package/src/server/valkey.js +134 -235
- package/tsconfig.docs.json +15 -0
- package/typedoc.json +20 -0
- package/jsdoc.json +0 -52
- package/src/client/components/core/ColorPalette.js +0 -5267
- package/src/client/components/core/JoyStick.js +0 -80
- package/src/client/components/default/RoutesDefault.js +0 -49
- package/src/client/sw/default.sw.js +0 -127
- package/src/client/sw/template.sw.js +0 -84
|
@@ -2,8 +2,8 @@ import { loggerFactory } from '../../server/logger.js';
|
|
|
2
2
|
import { DocumentService } from './document.service.js';
|
|
3
3
|
const logger = loggerFactory(import.meta);
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
post
|
|
5
|
+
class DocumentController {
|
|
6
|
+
static post = async (req, res, options) => {
|
|
7
7
|
try {
|
|
8
8
|
return res.status(200).json({
|
|
9
9
|
status: 'success',
|
|
@@ -16,8 +16,8 @@ const DocumentController = {
|
|
|
16
16
|
message: error.message,
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
|
-
}
|
|
20
|
-
get
|
|
19
|
+
};
|
|
20
|
+
static get = async (req, res, options) => {
|
|
21
21
|
try {
|
|
22
22
|
return res.status(200).json({
|
|
23
23
|
status: 'success',
|
|
@@ -30,8 +30,8 @@ const DocumentController = {
|
|
|
30
30
|
message: error.message,
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
|
-
}
|
|
34
|
-
delete
|
|
33
|
+
};
|
|
34
|
+
static delete = async (req, res, options) => {
|
|
35
35
|
try {
|
|
36
36
|
const result = await DocumentService.delete(req, res, options);
|
|
37
37
|
return res.status(200).json({
|
|
@@ -45,8 +45,8 @@ const DocumentController = {
|
|
|
45
45
|
message: error.message,
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
|
-
}
|
|
49
|
-
put
|
|
48
|
+
};
|
|
49
|
+
static put = async (req, res, options) => {
|
|
50
50
|
try {
|
|
51
51
|
const result = await DocumentService.put(req, res, options);
|
|
52
52
|
return res.status(200).json({
|
|
@@ -60,8 +60,8 @@ const DocumentController = {
|
|
|
60
60
|
message: error.message,
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
|
-
}
|
|
64
|
-
patch
|
|
63
|
+
};
|
|
64
|
+
static patch = async (req, res, options) => {
|
|
65
65
|
try {
|
|
66
66
|
const result = await DocumentService.patch(req, res, options);
|
|
67
67
|
return res.status(200).json({
|
|
@@ -75,7 +75,7 @@ const DocumentController = {
|
|
|
75
75
|
message: error.message,
|
|
76
76
|
});
|
|
77
77
|
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
80
|
|
|
81
81
|
export { DocumentController };
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { Schema, model, Types } from 'mongoose';
|
|
2
|
-
|
|
3
2
|
// https://mongoosejs.com/docs/2.7.x/docs/schematypes.html
|
|
4
|
-
|
|
5
3
|
const DocumentSchema = new Schema(
|
|
6
4
|
{
|
|
7
5
|
userId: {
|
|
@@ -35,13 +33,10 @@ const DocumentSchema = new Schema(
|
|
|
35
33
|
timestamps: true,
|
|
36
34
|
},
|
|
37
35
|
);
|
|
38
|
-
|
|
39
36
|
const DocumentModel = model('Document', DocumentSchema);
|
|
40
|
-
|
|
41
37
|
const ProviderSchema = DocumentSchema;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
populate: {
|
|
38
|
+
class DocumentDto {
|
|
39
|
+
static populate = {
|
|
45
40
|
file: () => {
|
|
46
41
|
return {
|
|
47
42
|
path: 'fileId',
|
|
@@ -68,34 +63,33 @@ const DocumentDto = {
|
|
|
68
63
|
},
|
|
69
64
|
};
|
|
70
65
|
},
|
|
71
|
-
}
|
|
72
|
-
getTotalCopyShareLinkCount
|
|
66
|
+
};
|
|
67
|
+
static getTotalCopyShareLinkCount = (document) => {
|
|
73
68
|
if (!document.share || !document.share.copyShareLinkEvent) return 0;
|
|
74
69
|
return document.share.copyShareLinkEvent.reduce((total, event) => total + (event.count || 0), 0);
|
|
75
|
-
}
|
|
70
|
+
};
|
|
76
71
|
/**
|
|
77
72
|
* Filter 'public' tag from tags array
|
|
78
73
|
* The 'public' tag is internal and should not be rendered to users
|
|
79
74
|
* @param {string[]} tags - Array of tags
|
|
80
75
|
* @returns {string[]} - Filtered tags without 'public'
|
|
81
76
|
*/
|
|
82
|
-
filterPublicTag
|
|
77
|
+
static filterPublicTag = (tags) => {
|
|
83
78
|
if (!tags || !Array.isArray(tags)) return [];
|
|
84
79
|
return tags.filter((tag) => tag !== 'public');
|
|
85
|
-
}
|
|
80
|
+
};
|
|
86
81
|
/**
|
|
87
82
|
* Extract isPublic boolean from tags array and return cleaned tags
|
|
88
83
|
* @param {string[]} tags - Array of tags potentially containing 'public'
|
|
89
84
|
* @returns {{ isPublic: boolean, tags: string[] }} - Object with isPublic flag and cleaned tags
|
|
90
85
|
*/
|
|
91
|
-
extractPublicFromTags
|
|
86
|
+
static extractPublicFromTags = (tags) => {
|
|
92
87
|
if (!tags || !Array.isArray(tags)) {
|
|
93
88
|
return { isPublic: false, tags: [] };
|
|
94
89
|
}
|
|
95
90
|
const hasPublicTag = tags.includes('public');
|
|
96
91
|
const cleanedTags = tags.filter((tag) => tag !== 'public');
|
|
97
92
|
return { isPublic: hasPublicTag, tags: cleanedTags };
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
93
|
+
};
|
|
94
|
+
}
|
|
101
95
|
export { DocumentSchema, DocumentModel, ProviderSchema, DocumentDto };
|
|
@@ -2,8 +2,8 @@ import { loggerFactory } from '../../server/logger.js';
|
|
|
2
2
|
import { FileService } from './file.service.js';
|
|
3
3
|
const logger = loggerFactory(import.meta);
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
post
|
|
5
|
+
class FileController {
|
|
6
|
+
static post = async (req, res, options) => {
|
|
7
7
|
try {
|
|
8
8
|
return res.status(200).json({
|
|
9
9
|
status: 'success',
|
|
@@ -16,8 +16,8 @@ const FileController = {
|
|
|
16
16
|
message: error.message,
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
|
-
}
|
|
20
|
-
get
|
|
19
|
+
};
|
|
20
|
+
static get = async (req, res, options) => {
|
|
21
21
|
try {
|
|
22
22
|
if (req && req.headers && req.headers.origin) {
|
|
23
23
|
res.set('Access-Control-Allow-Origin', req.headers.origin);
|
|
@@ -38,8 +38,8 @@ const FileController = {
|
|
|
38
38
|
message: error.message,
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
|
-
}
|
|
42
|
-
delete
|
|
41
|
+
};
|
|
42
|
+
static delete = async (req, res, options) => {
|
|
43
43
|
try {
|
|
44
44
|
const result = await FileService.delete(req, res, options);
|
|
45
45
|
return res.status(200).json({
|
|
@@ -53,7 +53,7 @@ const FileController = {
|
|
|
53
53
|
message: error.message,
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
58
|
|
|
59
59
|
export { FileController };
|
|
@@ -47,7 +47,7 @@ const ProviderSchema = FileSchema;
|
|
|
47
47
|
* @namespace FileModelServer.FileModelDto
|
|
48
48
|
* @memberof FileModelServer
|
|
49
49
|
*/
|
|
50
|
-
|
|
50
|
+
class FileModelDto {
|
|
51
51
|
/**
|
|
52
52
|
* Returns file metadata only (no buffer data).
|
|
53
53
|
* Used for list responses and API integration.
|
|
@@ -56,7 +56,7 @@ const FileModelDto = {
|
|
|
56
56
|
* @param {Object} file - File document from database.
|
|
57
57
|
* @returns {Object|null} File metadata object, or null if file is falsy.
|
|
58
58
|
*/
|
|
59
|
-
toMetadata
|
|
59
|
+
static toMetadata = (file) => {
|
|
60
60
|
if (!file) return null;
|
|
61
61
|
return {
|
|
62
62
|
_id: file._id,
|
|
@@ -68,7 +68,7 @@ const FileModelDto = {
|
|
|
68
68
|
createdAt: file.createdAt,
|
|
69
69
|
updatedAt: file.updatedAt,
|
|
70
70
|
};
|
|
71
|
-
}
|
|
71
|
+
};
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
74
|
* Returns file with complete data.
|
|
@@ -78,7 +78,7 @@ const FileModelDto = {
|
|
|
78
78
|
* @param {Object} file - File document from database.
|
|
79
79
|
* @returns {Object|null} Complete file object with buffer data, or null if file is falsy.
|
|
80
80
|
*/
|
|
81
|
-
toFull
|
|
81
|
+
static toFull = (file) => {
|
|
82
82
|
if (!file) return null;
|
|
83
83
|
return {
|
|
84
84
|
_id: file._id,
|
|
@@ -92,7 +92,7 @@ const FileModelDto = {
|
|
|
92
92
|
createdAt: file.createdAt,
|
|
93
93
|
updatedAt: file.updatedAt,
|
|
94
94
|
};
|
|
95
|
-
}
|
|
95
|
+
};
|
|
96
96
|
|
|
97
97
|
/**
|
|
98
98
|
* Transforms array of files to metadata only.
|
|
@@ -101,10 +101,10 @@ const FileModelDto = {
|
|
|
101
101
|
* @param {Array} files - Array of file documents.
|
|
102
102
|
* @returns {Array} Array of file metadata objects.
|
|
103
103
|
*/
|
|
104
|
-
toMetadataArray
|
|
104
|
+
static toMetadataArray = (files) => {
|
|
105
105
|
if (!Array.isArray(files)) return [];
|
|
106
106
|
return files.map((file) => FileModelDto.toMetadata(file));
|
|
107
|
-
}
|
|
107
|
+
};
|
|
108
108
|
|
|
109
109
|
/**
|
|
110
110
|
* Ensures UTF-8 encoding for filenames.
|
|
@@ -114,14 +114,14 @@ const FileModelDto = {
|
|
|
114
114
|
* @param {string} filename - Raw filename from upload.
|
|
115
115
|
* @returns {string} UTF-8 encoded filename.
|
|
116
116
|
*/
|
|
117
|
-
normalizeFilename
|
|
117
|
+
static normalizeFilename = (filename) => {
|
|
118
118
|
if (!filename) return '';
|
|
119
119
|
// Ensure string and normalize to UTF-8
|
|
120
120
|
let normalized = String(filename);
|
|
121
121
|
// Replace any incorrectly encoded sequences
|
|
122
122
|
normalized = Buffer.from(normalized, 'utf8').toString('utf8');
|
|
123
123
|
return normalized;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
126
|
|
|
127
127
|
export { FileSchema, FileModel, ProviderSchema, FileModelDto };
|
|
@@ -27,7 +27,7 @@ const logger = loggerFactory(import.meta);
|
|
|
27
27
|
* @namespace FileServiceServer.FileServiceDto
|
|
28
28
|
* @memberof FileServiceServer
|
|
29
29
|
*/
|
|
30
|
-
|
|
30
|
+
class FileServiceDto {
|
|
31
31
|
/**
|
|
32
32
|
* Returns file metadata only (no buffer data).
|
|
33
33
|
* Used for list responses and API integration.
|
|
@@ -36,7 +36,7 @@ const FileServiceDto = {
|
|
|
36
36
|
* @param {Object} file - File document from database.
|
|
37
37
|
* @returns {Object|null} File metadata object, or null if file is falsy.
|
|
38
38
|
*/
|
|
39
|
-
toMetadata
|
|
39
|
+
static toMetadata = (file) => {
|
|
40
40
|
if (!file) return null;
|
|
41
41
|
return {
|
|
42
42
|
_id: file._id,
|
|
@@ -48,7 +48,7 @@ const FileServiceDto = {
|
|
|
48
48
|
createdAt: file.createdAt,
|
|
49
49
|
updatedAt: file.updatedAt,
|
|
50
50
|
};
|
|
51
|
-
}
|
|
51
|
+
};
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
54
|
* Transforms array of files to metadata only.
|
|
@@ -57,10 +57,10 @@ const FileServiceDto = {
|
|
|
57
57
|
* @param {Array} files - Array of file documents.
|
|
58
58
|
* @returns {Array} Array of file metadata objects.
|
|
59
59
|
*/
|
|
60
|
-
toMetadataArray
|
|
60
|
+
static toMetadataArray = (files) => {
|
|
61
61
|
if (!Array.isArray(files)) return [];
|
|
62
62
|
return files.map((file) => FileServiceDto.toMetadata(file));
|
|
63
|
-
}
|
|
63
|
+
};
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
66
|
* Ensures UTF-8 encoding for filenames.
|
|
@@ -70,14 +70,14 @@ const FileServiceDto = {
|
|
|
70
70
|
* @param {string} filename - Raw filename from upload.
|
|
71
71
|
* @returns {string} UTF-8 encoded filename.
|
|
72
72
|
*/
|
|
73
|
-
normalizeFilename
|
|
73
|
+
static normalizeFilename = (filename) => {
|
|
74
74
|
if (!filename) return '';
|
|
75
75
|
// Ensure string and normalize to UTF-8
|
|
76
76
|
let normalized = String(filename);
|
|
77
77
|
// Replace any incorrectly encoded sequences
|
|
78
78
|
normalized = Buffer.from(normalized, 'utf8').toString('utf8');
|
|
79
79
|
return normalized;
|
|
80
|
-
}
|
|
80
|
+
};
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
83
|
* Get select fields for metadata-only queries.
|
|
@@ -86,17 +86,17 @@ const FileServiceDto = {
|
|
|
86
86
|
* @memberof FileServiceServer.FileServiceDto
|
|
87
87
|
* @returns {string} Space-separated list of field names for metadata selection.
|
|
88
88
|
*/
|
|
89
|
-
metadataSelect
|
|
89
|
+
static metadataSelect = () => {
|
|
90
90
|
return '_id name mimetype size encoding md5 cid createdAt updatedAt';
|
|
91
|
-
}
|
|
92
|
-
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
95
|
* File Factory for file extraction, upload, and creation utilities.
|
|
96
96
|
* @namespace FileServiceServer.FileFactory
|
|
97
97
|
* @memberof FileServiceServer
|
|
98
98
|
*/
|
|
99
|
-
|
|
99
|
+
class FileFactory {
|
|
100
100
|
/**
|
|
101
101
|
* Extract files from request.
|
|
102
102
|
* Handles both standard 'file' field and custom fields.
|
|
@@ -105,7 +105,7 @@ const FileFactory = {
|
|
|
105
105
|
* @param {Object} req - Express request object with files.
|
|
106
106
|
* @returns {Array} Array of extracted file objects.
|
|
107
107
|
*/
|
|
108
|
-
filesExtract
|
|
108
|
+
static filesExtract = (req) => {
|
|
109
109
|
const files = [];
|
|
110
110
|
if (!req.files || Object.keys(req.files).length === 0) {
|
|
111
111
|
return files;
|
|
@@ -139,7 +139,7 @@ const FileFactory = {
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
return files;
|
|
142
|
-
}
|
|
142
|
+
};
|
|
143
143
|
|
|
144
144
|
/**
|
|
145
145
|
* Upload files to database with UTF-8 encoding.
|
|
@@ -150,7 +150,7 @@ const FileFactory = {
|
|
|
150
150
|
* @param {import('mongoose').Model} File - Mongoose File model.
|
|
151
151
|
* @returns {Promise<Array>} Array of uploaded file metadata objects.
|
|
152
152
|
*/
|
|
153
|
-
|
|
153
|
+
static async upload(req, File) {
|
|
154
154
|
const results = FileFactory.filesExtract(req);
|
|
155
155
|
let index = -1;
|
|
156
156
|
|
|
@@ -172,7 +172,7 @@ const FileFactory = {
|
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
return results;
|
|
175
|
-
}
|
|
175
|
+
}
|
|
176
176
|
|
|
177
177
|
/**
|
|
178
178
|
* Convert string to hexadecimal.
|
|
@@ -181,9 +181,9 @@ const FileFactory = {
|
|
|
181
181
|
* @param {string} [raw=''] - Raw string to convert.
|
|
182
182
|
* @returns {string} Hexadecimal representation of the string.
|
|
183
183
|
*/
|
|
184
|
-
hex
|
|
184
|
+
static hex = (raw = '') => {
|
|
185
185
|
return Buffer.from(raw, 'utf8').toString('hex');
|
|
186
|
-
}
|
|
186
|
+
};
|
|
187
187
|
|
|
188
188
|
/**
|
|
189
189
|
* Get MIME type from file path based on extension.
|
|
@@ -192,7 +192,7 @@ const FileFactory = {
|
|
|
192
192
|
* @param {string} path - File path or filename with extension.
|
|
193
193
|
* @returns {string} MIME type string.
|
|
194
194
|
*/
|
|
195
|
-
getMymeTypeFromPath
|
|
195
|
+
static getMymeTypeFromPath = (path) => {
|
|
196
196
|
const ext = String(path || '')
|
|
197
197
|
.toLowerCase()
|
|
198
198
|
.split('.')
|
|
@@ -210,7 +210,7 @@ const FileFactory = {
|
|
|
210
210
|
json: 'application/json',
|
|
211
211
|
};
|
|
212
212
|
return mimeTypes[ext] || 'application/octet-stream';
|
|
213
|
-
}
|
|
213
|
+
};
|
|
214
214
|
|
|
215
215
|
/**
|
|
216
216
|
* Create file object with proper encoding.
|
|
@@ -220,7 +220,7 @@ const FileFactory = {
|
|
|
220
220
|
* @param {string} [name=''] - File name.
|
|
221
221
|
* @returns {Object} File object with name, data, size, encoding, mimetype, and md5.
|
|
222
222
|
*/
|
|
223
|
-
create
|
|
223
|
+
static create = (data = Buffer.from([]), name = '') => {
|
|
224
224
|
const normalizedName = FileServiceDto.normalizeFilename(name);
|
|
225
225
|
|
|
226
226
|
return {
|
|
@@ -234,8 +234,8 @@ const FileFactory = {
|
|
|
234
234
|
md5: crypto.createHash('md5').update(data).digest('hex'),
|
|
235
235
|
cid: undefined,
|
|
236
236
|
};
|
|
237
|
-
}
|
|
238
|
-
}
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
239
|
|
|
240
240
|
/**
|
|
241
241
|
* File cleanup utilities for preventing orphaned files.
|
|
@@ -244,7 +244,7 @@ const FileFactory = {
|
|
|
244
244
|
* @namespace FileServiceServer.FileCleanup
|
|
245
245
|
* @memberof FileServiceServer
|
|
246
246
|
*/
|
|
247
|
-
|
|
247
|
+
class FileCleanup {
|
|
248
248
|
/**
|
|
249
249
|
* Clean up old file references when document fields are updated.
|
|
250
250
|
* Deletes old files that are being replaced by new file IDs.
|
|
@@ -258,7 +258,7 @@ const FileCleanup = {
|
|
|
258
258
|
* @param {import('mongoose').Model} options.File - Mongoose File model.
|
|
259
259
|
* @returns {Promise<Array>} Array of deleted file IDs.
|
|
260
260
|
*/
|
|
261
|
-
cleanupReplacedFiles
|
|
261
|
+
static cleanupReplacedFiles = async ({ oldDoc, newData, fileFields, File }) => {
|
|
262
262
|
const deletedFileIds = [];
|
|
263
263
|
|
|
264
264
|
for (const field of fileFields) {
|
|
@@ -281,7 +281,7 @@ const FileCleanup = {
|
|
|
281
281
|
}
|
|
282
282
|
|
|
283
283
|
return deletedFileIds;
|
|
284
|
-
}
|
|
284
|
+
};
|
|
285
285
|
|
|
286
286
|
/**
|
|
287
287
|
* Delete all files referenced in a document.
|
|
@@ -297,7 +297,7 @@ const FileCleanup = {
|
|
|
297
297
|
* @param {import('mongoose').Model} options.File - Mongoose File model.
|
|
298
298
|
* @returns {Promise<Array>} Array of deleted file IDs.
|
|
299
299
|
*/
|
|
300
|
-
deleteDocumentFiles
|
|
300
|
+
static deleteDocumentFiles = async ({ doc, fileFields, File }) => {
|
|
301
301
|
const deletedFileIds = [];
|
|
302
302
|
|
|
303
303
|
for (const field of fileFields) {
|
|
@@ -318,15 +318,15 @@ const FileCleanup = {
|
|
|
318
318
|
}
|
|
319
319
|
|
|
320
320
|
return deletedFileIds;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
323
|
|
|
324
324
|
/**
|
|
325
325
|
* File Service for handling REST API file operations.
|
|
326
326
|
* @namespace FileServiceServer.FileService
|
|
327
327
|
* @memberof FileServiceServer
|
|
328
328
|
*/
|
|
329
|
-
|
|
329
|
+
class FileService {
|
|
330
330
|
/**
|
|
331
331
|
* POST - Upload files.
|
|
332
332
|
* Returns metadata-only response (no buffer data).
|
|
@@ -338,7 +338,7 @@ const FileService = {
|
|
|
338
338
|
* @param {Object} options - Request options containing host and path.
|
|
339
339
|
* @returns {Promise<Array>} Array of uploaded file metadata objects.
|
|
340
340
|
*/
|
|
341
|
-
post
|
|
341
|
+
static post = async (req, res, options) => {
|
|
342
342
|
// Check that user is authenticated and not a guest
|
|
343
343
|
if (!req.auth || !req.auth.user || req.auth.user.role === 'guest') {
|
|
344
344
|
throw new Error('Authentication required. Guest users cannot upload files.');
|
|
@@ -349,7 +349,7 @@ const FileService = {
|
|
|
349
349
|
|
|
350
350
|
const uploadedFiles = await FileFactory.upload(req, File);
|
|
351
351
|
return FileServiceDto.toMetadataArray(uploadedFiles);
|
|
352
|
-
}
|
|
352
|
+
};
|
|
353
353
|
|
|
354
354
|
/**
|
|
355
355
|
* GET - Retrieve files.
|
|
@@ -365,7 +365,7 @@ const FileService = {
|
|
|
365
365
|
* @returns {Promise<Array|Buffer>} Array of file metadata objects or Buffer for blob endpoint.
|
|
366
366
|
* @throws {Error} If file not found or user not authorized.
|
|
367
367
|
*/
|
|
368
|
-
get
|
|
368
|
+
static get = async (req, res, options) => {
|
|
369
369
|
/** @type {import('./file.model.js').FileModel} */
|
|
370
370
|
const File = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.File;
|
|
371
371
|
/** @type {import('../document/document.model.js').DocumentModel} */
|
|
@@ -467,7 +467,7 @@ const FileService = {
|
|
|
467
467
|
return FileServiceDto.toMetadataArray(files);
|
|
468
468
|
}
|
|
469
469
|
}
|
|
470
|
-
}
|
|
470
|
+
};
|
|
471
471
|
|
|
472
472
|
/**
|
|
473
473
|
* DELETE - Remove files.
|
|
@@ -480,7 +480,7 @@ const FileService = {
|
|
|
480
480
|
* @returns {Promise<Object>} Deleted file metadata object.
|
|
481
481
|
* @throws {Error} If file not found.
|
|
482
482
|
*/
|
|
483
|
-
delete
|
|
483
|
+
static delete = async (req, res, options) => {
|
|
484
484
|
/** @type {import('./file.model.js').FileModel} */
|
|
485
485
|
const File = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.File;
|
|
486
486
|
|
|
@@ -491,7 +491,7 @@ const FileService = {
|
|
|
491
491
|
}
|
|
492
492
|
|
|
493
493
|
return FileServiceDto.toMetadata(result);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
496
|
|
|
497
497
|
export { FileService, FileFactory, FileServiceDto, FileCleanup };
|
|
@@ -3,8 +3,8 @@ import { TestService } from './test.service.js';
|
|
|
3
3
|
|
|
4
4
|
const logger = loggerFactory(import.meta);
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
post
|
|
6
|
+
class TestController {
|
|
7
|
+
static post = async (req, res, options) => {
|
|
8
8
|
try {
|
|
9
9
|
return res.status(200).json({
|
|
10
10
|
status: 'success',
|
|
@@ -17,8 +17,8 @@ const TestController = {
|
|
|
17
17
|
message: error.message,
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
|
-
}
|
|
21
|
-
get
|
|
20
|
+
};
|
|
21
|
+
static get = async (req, res, options) => {
|
|
22
22
|
try {
|
|
23
23
|
const result = await TestService.get(req, res, options);
|
|
24
24
|
if (result)
|
|
@@ -37,8 +37,8 @@ const TestController = {
|
|
|
37
37
|
message: error.message,
|
|
38
38
|
});
|
|
39
39
|
}
|
|
40
|
-
}
|
|
41
|
-
delete
|
|
40
|
+
};
|
|
41
|
+
static delete = async (req, res, options) => {
|
|
42
42
|
try {
|
|
43
43
|
const result = await TestService.delete(req, res, options);
|
|
44
44
|
|
|
@@ -53,7 +53,7 @@ const TestController = {
|
|
|
53
53
|
message: error.message,
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
58
|
|
|
59
59
|
export { TestController };
|
|
@@ -5,14 +5,14 @@ import { getYouTubeID, validatePassword } from '../../client/components/core/Com
|
|
|
5
5
|
|
|
6
6
|
const logger = loggerFactory(import.meta);
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
post
|
|
8
|
+
class TestService {
|
|
9
|
+
static post = async (req, res, options) => {
|
|
10
10
|
switch (req.params.id) {
|
|
11
11
|
default:
|
|
12
12
|
break;
|
|
13
13
|
}
|
|
14
|
-
}
|
|
15
|
-
get
|
|
14
|
+
};
|
|
15
|
+
static get = async (req, res, options) => {
|
|
16
16
|
switch (req.params.id) {
|
|
17
17
|
case 'verify-email':
|
|
18
18
|
return validator.isEmail(req.query.email);
|
|
@@ -23,13 +23,13 @@ const TestService = {
|
|
|
23
23
|
|
|
24
24
|
default:
|
|
25
25
|
}
|
|
26
|
-
}
|
|
27
|
-
delete
|
|
26
|
+
};
|
|
27
|
+
static delete = async (req, res, options) => {
|
|
28
28
|
switch (req.params.id) {
|
|
29
29
|
default:
|
|
30
30
|
break;
|
|
31
31
|
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
34
|
|
|
35
35
|
export { TestService };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import mongoose from 'mongoose';
|
|
2
|
+
import { UserDto } from './user.model.js';
|
|
3
|
+
import { ValkeyAPI } from '../../server/valkey.js';
|
|
4
|
+
import { hashPassword, getBearerToken, jwtSign } from '../../server/auth.js';
|
|
5
|
+
|
|
6
|
+
// ─── TTL ──────────────────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
const _guestTtlMs = () => {
|
|
9
|
+
const minutes = Number.parseInt(process.env.REFRESH_EXPIRE_MINUTES || '60', 10);
|
|
10
|
+
return Number.isFinite(minutes) && minutes > 0 ? minutes * 60 * 1000 : 60 * 60 * 1000;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// ─── Domain helpers ───────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Constructs a new ephemeral guest user object.
|
|
17
|
+
* This is domain logic specific to the guest lifecycle; it does not belong
|
|
18
|
+
* in the generic Valkey storage module.
|
|
19
|
+
*
|
|
20
|
+
* @param {{ host?: string }} options
|
|
21
|
+
* @returns {object}
|
|
22
|
+
*/
|
|
23
|
+
const buildGuestUser = (options) => {
|
|
24
|
+
const now = new Date().toISOString();
|
|
25
|
+
const _id = new mongoose.Types.ObjectId().toString();
|
|
26
|
+
const role = 'guest';
|
|
27
|
+
return {
|
|
28
|
+
_id: `${role}${_id}`,
|
|
29
|
+
username: `${role}${_id.slice(-5)}`,
|
|
30
|
+
email: `${_id}@${options.host || 'localhost'}`,
|
|
31
|
+
password: hashPassword(process.env.JWT_SECRET),
|
|
32
|
+
role,
|
|
33
|
+
emailConfirmed: false,
|
|
34
|
+
profileImageId: null,
|
|
35
|
+
publicKey: [],
|
|
36
|
+
phoneNumbers: [],
|
|
37
|
+
activeSessions: [],
|
|
38
|
+
failedLoginAttempts: 0,
|
|
39
|
+
recoverTimeOut: null,
|
|
40
|
+
lastLoginDate: null,
|
|
41
|
+
createdAt: now,
|
|
42
|
+
updatedAt: now,
|
|
43
|
+
guestSessionExpiresAt: Date.now() + _guestTtlMs(),
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Projects a user object to the public-safe fields defined by UserDto.select.get().
|
|
49
|
+
* Keeps this logic next to the guest domain instead of in a generic utility.
|
|
50
|
+
*
|
|
51
|
+
* @param {object} user
|
|
52
|
+
* @returns {object}
|
|
53
|
+
*/
|
|
54
|
+
const _toPublicUser = (user) => {
|
|
55
|
+
const select = UserDto.select.get();
|
|
56
|
+
return Object.fromEntries(
|
|
57
|
+
Object.keys(select)
|
|
58
|
+
.filter((k) => select[k] === 1 && k in user)
|
|
59
|
+
.map((k) => [k, user[k]]),
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const _withRefreshedExpiry = (user) => ({ ...user, guestSessionExpiresAt: Date.now() + _guestTtlMs() });
|
|
64
|
+
|
|
65
|
+
// ─── Service ──────────────────────────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
class GuestService {
|
|
68
|
+
static async create(req, options) {
|
|
69
|
+
const user = buildGuestUser(options);
|
|
70
|
+
|
|
71
|
+
await ValkeyAPI.set(options, user.email, user, _guestTtlMs());
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
token: jwtSign(
|
|
75
|
+
UserDto.auth.payload(user, null, req.ip, req.headers['user-agent'], options.host, options.path),
|
|
76
|
+
options,
|
|
77
|
+
),
|
|
78
|
+
user: _toPublicUser(user),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
static async auth(req, options) {
|
|
83
|
+
const user = await ValkeyAPI.get(options, req.auth.user.email);
|
|
84
|
+
if (!user) throw new Error('guest user expired');
|
|
85
|
+
|
|
86
|
+
const expiresAt = Number(user.guestSessionExpiresAt || 0);
|
|
87
|
+
if (expiresAt && expiresAt <= Date.now()) throw new Error('guest user expired');
|
|
88
|
+
|
|
89
|
+
const refreshed = _withRefreshedExpiry(user);
|
|
90
|
+
await ValkeyAPI.set(options, refreshed.email, refreshed, _guestTtlMs());
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
user: _toPublicUser(refreshed),
|
|
94
|
+
token: getBearerToken(req),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export { GuestService };
|