@sync-in/server 1.9.3 → 1.9.6
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/CHANGELOG.md +12 -0
- package/package.json +7 -7
- package/server/applications/files/interfaces/only-office-config.interface.js.map +1 -1
- package/server/applications/files/services/files-methods.service.spec.js +2 -4
- package/server/applications/files/services/files-methods.service.spec.js.map +1 -1
- package/server/applications/files/services/files-recents.service.js +4 -0
- package/server/applications/files/services/files-recents.service.js.map +1 -1
- package/server/applications/files/services/files-scheduler.service.js +23 -4
- package/server/applications/files/services/files-scheduler.service.js.map +1 -1
- package/server/applications/spaces/services/spaces-scheduler.service.js +9 -1
- package/server/applications/spaces/services/spaces-scheduler.service.js.map +1 -1
- package/static/{chunk-YXWF2DGF.js → chunk-2F42MZQ5.js} +1 -1
- package/static/{chunk-KBWK65KM.js → chunk-2U5VKTML.js} +1 -1
- package/static/{chunk-XAIOGRBO.js → chunk-3AR5VNJE.js} +1 -1
- package/static/chunk-3WS72A6C.js +1 -0
- package/static/{chunk-6VJI4X2A.js → chunk-4GBA6EJ4.js} +1 -1
- package/static/{chunk-O7UXVNR2.js → chunk-4ZKAVMB4.js} +1 -1
- package/static/{chunk-JEVBUJQ4.js → chunk-5ATJIR5S.js} +1 -1
- package/static/{chunk-MZBO5PAR.js → chunk-5GIWZKNS.js} +1 -1
- package/static/{chunk-4OV3SAUS.js → chunk-5KVI243T.js} +1 -1
- package/static/{chunk-4EUHBTWV.js → chunk-5NFH4E2B.js} +1 -1
- package/static/{chunk-PKU4IIIR.js → chunk-7HL5Z6PF.js} +1 -1
- package/static/{chunk-4DF2SQD4.js → chunk-7QYALK5T.js} +1 -1
- package/static/{chunk-ZC5ZDCDC.js → chunk-ANH4VNOS.js} +1 -1
- package/static/{chunk-CURVLK7L.js → chunk-AYYJZMBE.js} +1 -1
- package/static/{chunk-A7R246NW.js → chunk-B2A4HNDC.js} +1 -1
- package/static/{chunk-ZRBLCAOK.js → chunk-B4TDS6AQ.js} +1 -1
- package/static/{chunk-WJYVS27M.js → chunk-BVKDW5XO.js} +1 -1
- package/static/{chunk-GDPJRUVU.js → chunk-BX3QZ7IL.js} +1 -1
- package/static/{chunk-ZPI7RQ2S.js → chunk-C5T7RZSD.js} +1 -1
- package/static/{chunk-7NI353LS.js → chunk-CHMDM2ZW.js} +1 -1
- package/static/{chunk-IUJ4IK26.js → chunk-CUC7R6C2.js} +1 -1
- package/static/{chunk-A7DSX7VP.js → chunk-D6QWQHWE.js} +1 -1
- package/static/{chunk-U75PLYIJ.js → chunk-DK2LAJEL.js} +1 -1
- package/static/{chunk-TGHBDJZA.js → chunk-DQAQUSVW.js} +1 -1
- package/static/{chunk-QUSS6SUC.js → chunk-DU4Q4RWJ.js} +1 -1
- package/static/{chunk-MBFMTBVJ.js → chunk-E5C4QRNQ.js} +2 -2
- package/static/{chunk-R6VB3INJ.js → chunk-EPDWJEPD.js} +1 -1
- package/static/{chunk-L3PDWJZ3.js → chunk-FCR5AEHR.js} +1 -1
- package/static/{chunk-5NHB7SV3.js → chunk-FEQUP26G.js} +1 -1
- package/static/{chunk-FLPZB3OX.js → chunk-FSGT46LM.js} +1 -1
- package/static/{chunk-XXYMVRSH.js → chunk-GLPKRULI.js} +1 -1
- package/static/{chunk-SDR3UG2F.js → chunk-GRV44RYI.js} +1 -1
- package/static/{chunk-FXM7XXWA.js → chunk-GYYJ4FWN.js} +1 -1
- package/static/{chunk-33WFRCUP.js → chunk-HB5DC7RJ.js} +1 -1
- package/static/{chunk-NFIES7BC.js → chunk-HLKZCMKV.js} +1 -1
- package/static/{chunk-TVJQXN73.js → chunk-IBC7CFBQ.js} +1 -1
- package/static/{chunk-DDRGLHOP.js → chunk-IIKL33TV.js} +1 -1
- package/static/{chunk-A6J6SOM6.js → chunk-JYHTSSKW.js} +1 -1
- package/static/{chunk-CAZSNVMS.js → chunk-KAAFVHYE.js} +1 -1
- package/static/{chunk-H4RLHI3Y.js → chunk-KWKZN53T.js} +1 -1
- package/static/chunk-MGMDT4VN.js +1 -0
- package/static/chunk-MWUUM2NK.js +13 -0
- package/static/{chunk-Z6RJZIDG.js → chunk-NHMYAVJK.js} +1 -1
- package/static/{chunk-YTBSB2GE.js → chunk-NQCKX2AD.js} +1 -1
- package/static/{chunk-27XEAHMV.js → chunk-O233BXWK.js} +1 -1
- package/static/{chunk-RJOHDAPM.js → chunk-ODAQRAPO.js} +1 -1
- package/static/{chunk-2LHHXDD5.js → chunk-OVUMPMVM.js} +1 -1
- package/static/chunk-Q6B4OVER.js +5 -0
- package/static/{chunk-NK2NMAJI.js → chunk-QKMN3S4M.js} +1 -1
- package/static/{chunk-FRBTL2ER.js → chunk-QUUQOBTF.js} +1 -1
- package/static/chunk-QV5LQKTS.js +1 -0
- package/static/{chunk-XHQEF2IX.js → chunk-S4UTSOPV.js} +1 -1
- package/static/{chunk-5HCVWZMA.js → chunk-SF6Q6VRC.js} +1 -1
- package/static/{chunk-S6YKBWJE.js → chunk-TOCCCZP2.js} +1 -1
- package/static/{chunk-HE6EDXWI.js → chunk-UO7ATVQG.js} +1 -1
- package/static/{chunk-4KXJ6C4N.js → chunk-X5UDV4ZB.js} +1 -1
- package/static/{chunk-VO4WVT6K.js → chunk-XIQXRSZ2.js} +1 -1
- package/static/chunk-YYTDPI5S.js +1 -0
- package/static/{chunk-ASBPYTLT.js → chunk-Z2KBIZ5D.js} +1 -1
- package/static/{chunk-K3MOXDU5.js → chunk-ZCOEP4O2.js} +1 -1
- package/static/index.html +1 -1
- package/static/main-ODUA232E.js +11 -0
- package/static/chunk-2XY4PMI5.js +0 -1
- package/static/chunk-3LVFDMTN.js +0 -1
- package/static/chunk-UUX3M6DC.js +0 -1
- package/static/chunk-VJ2HWQRJ.js +0 -5
- package/static/chunk-W72JYHOH.js +0 -1
- package/static/chunk-ZERBTNFW.js +0 -13
- package/static/main-FE6GWZXU.js +0 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
|
|
2
|
+
## [1.9.6](https://github.com/Sync-in/server/compare/v1.9.5...v1.9.6) (2025-12-16)
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **backend:files:** skip adding recents for trashed files ([c445196](https://github.com/Sync-in/server/commit/c445196914b2d351fba9218698a24496b1d6036c))
|
|
7
|
+
* **backend:schedulers:** resolve scheduled methods being skipped because of @Timeout decorator overlap ([50f4140](https://github.com/Sync-in/server/commit/50f4140a7b0b478e6b499ea8884b43f13595bb71))
|
|
8
|
+
* **frontend:files:** enable autoplay for video in media viewer component ([20fe25f](https://github.com/Sync-in/server/commit/20fe25fba00987994076d09489febd5593e08cef))
|
|
9
|
+
* **frontend:files:** remove hidden class from buttons for consistent visibility across breakpoints ([a60538a](https://github.com/Sync-in/server/commit/a60538ad01c675dacdac7ed4d80ca2bdf5f369ba))
|
|
10
|
+
* **frontend:files:** update file metadata timestamps on save and align OnlyOffice state change handlers ([db768e1](https://github.com/Sync-in/server/commit/db768e14452f4712df9f443350c214e0700b7270))
|
|
11
|
+
* **frontend:search:** improve search input layout and update filter button visibility for responsiveness ([09ebce6](https://github.com/Sync-in/server/commit/09ebce612fa2d72699a4d60bf9896f8e3c0fc4e4))
|
|
12
|
+
* **frontend:spaces:** show disabled space message to space managers ([f8bcdf7](https://github.com/Sync-in/server/commit/f8bcdf7fdd4b25abc2ba4b74715adbb0ae04a3e3))
|
|
13
|
+
|
|
2
14
|
## [1.9.3](https://github.com/Sync-in/server/compare/v1.9.1...v1.9.3) (2025-12-07)
|
|
3
15
|
|
|
4
16
|
### Security Fixes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sync-in/server",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.6",
|
|
4
4
|
"description": "The secure, open-source platform for file storage, sharing, collaboration, and sync",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Johan Legrand",
|
|
@@ -83,11 +83,11 @@
|
|
|
83
83
|
"@nestjs/common": "11.1.9",
|
|
84
84
|
"@nestjs/config": "4.0.2",
|
|
85
85
|
"@nestjs/core": "11.1.9",
|
|
86
|
-
"@nestjs/jwt": "11.0.
|
|
86
|
+
"@nestjs/jwt": "11.0.2",
|
|
87
87
|
"@nestjs/passport": "11.0.5",
|
|
88
88
|
"@nestjs/platform-fastify": "11.1.9",
|
|
89
89
|
"@nestjs/platform-socket.io": "11.1.9",
|
|
90
|
-
"@nestjs/schedule": "6.0
|
|
90
|
+
"@nestjs/schedule": "6.1.0",
|
|
91
91
|
"@nestjs/websockets": "11.1.9",
|
|
92
92
|
"@socket.io/cluster-adapter": "0.3.0",
|
|
93
93
|
"@socket.io/redis-adapter": "8.3.0",
|
|
@@ -97,15 +97,15 @@
|
|
|
97
97
|
"class-transformer": "0.5.1",
|
|
98
98
|
"class-validator": "0.14.3",
|
|
99
99
|
"deepmerge": "4.3.1",
|
|
100
|
-
"drizzle-kit": "0.31.
|
|
100
|
+
"drizzle-kit": "0.31.8",
|
|
101
101
|
"drizzle-orm": "0.44.7",
|
|
102
|
-
"fast-xml-parser": "5.3.
|
|
102
|
+
"fast-xml-parser": "5.3.3",
|
|
103
103
|
"fs-extra": "11.3.2",
|
|
104
104
|
"html-to-text": "9.0.5",
|
|
105
105
|
"js-yaml": "4.1.1",
|
|
106
|
-
"ldapts": "8.0.
|
|
106
|
+
"ldapts": "8.0.25",
|
|
107
107
|
"mime-types": "3.0.2",
|
|
108
|
-
"mysql2": "3.
|
|
108
|
+
"mysql2": "3.16.0",
|
|
109
109
|
"nestjs-pino": "4.5.0",
|
|
110
110
|
"nodemailer": "7.0.11",
|
|
111
111
|
"passport-http": "0.3.0",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../backend/src/applications/files/interfaces/only-office-config.interface.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { FILE_MODE } from '../constants/operations'\n\nexport interface OnlyOfficeReqConfig {\n documentServerUrl: string\n config: OnlyOfficeConfig\n}\n\nexport interface OnlyOfficeConvertForm {\n key: string\n url: string\n outputtype: string\n filetype: string\n async: boolean\n token?: string\n}\n\nexport interface OnlyOfficeConfig {\n documentType?: string\n token?: string\n type?: 'mobile' | 'desktop'\n height?: string\n width?: string\n document?: {\n fileType: string\n key: string\n referenceData?: {\n fileKey: string\n instanceId: string\n }\n title: string\n url: string\n info?: {\n owner?: string\n uploaded?: string\n favorite?: boolean\n folder?: string\n sharingSettings?: any[]\n }\n permissions?: {\n /**\n * @deprecated Deprecated since version 5.5, please add the onRequestRestore field instead.\n */\n changeHistory?: boolean\n chat?: boolean\n comment?: boolean\n commentGroups?: any\n copy?: boolean\n deleteCommentAuthorOnly?: boolean\n download?: boolean\n edit?: boolean\n editCommentAuthorOnly?: boolean\n fillForms?: boolean\n modifyContentControl?: boolean\n modifyFilter?: boolean\n print?: boolean\n protect?: boolean\n review?: boolean\n reviewGroups?: string[]\n userInfoGroups?: string[]\n }\n }\n editorConfig?: {\n actionLink?: any\n callbackUrl?: string\n coEditing?: {\n mode: string\n change: boolean\n }\n createUrl?: string\n lang?: string\n mode?: FILE_MODE\n recent?: any[]\n region?: string\n templates?: any[]\n user?: {\n group?: string\n id?: string\n image?: string\n name?: string\n }\n customization?: {\n anonymous?: {\n request?: boolean\n label?: string\n }\n autosave?: boolean\n forcesave?: boolean\n close?: {\n visible: boolean\n text: string\n }\n comments?: boolean\n compactHeader?: boolean\n compactToolbar?: boolean\n compatibleFeatures?: boolean\n customer?: {\n address?: string\n info?: string\n logo?: string\n logoDark?: string\n mail?: string\n name?: string\n phone?: string\n www?: string\n }\n features?: any\n feedback?: any\n goback?: any\n help?: boolean\n hideNotes?: boolean\n hideRightMenu?: boolean\n hideRulers?: boolean\n integrationMode?: string\n logo?: {\n image?: string\n imageDark?: string\n imageLight?: string\n imageEmbedded?: string\n url?: string\n visible?: boolean\n }\n macros?: boolean\n macrosMode?: string\n mentionShare?: boolean\n mobileForceView?: boolean\n plugins?: boolean\n review?: {\n hideReviewDisplay?: boolean\n hoverMode?: boolean\n reviewDisplay?: string\n showReviewChanges?: boolean\n trackChanges?: boolean\n }\n submitForm?: boolean\n toolbarHideFileName?: boolean\n toolbarNoTabs?: boolean\n uiTheme?: string\n unit?: string\n zoom?: number\n about?: boolean\n }\n embedded?: {\n embedUrl?: string\n fullscreenUrl?: string\n saveUrl?: string\n shareUrl?: string\n toolbarDocked?: string\n }\n plugins?: {\n autostart?: string[]\n options?: {\n all?: any\n pluginGuid: any\n }\n pluginsData?: string[]\n }\n }\n events?: {\n onAppReady?: (event: object) => void\n onCollaborativeChanges?: (event: object) => void\n onDocumentReady?: (event: object) => void\n onDocumentStateChange?: (event:
|
|
1
|
+
{"version":3,"sources":["../../../../../backend/src/applications/files/interfaces/only-office-config.interface.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { FILE_MODE } from '../constants/operations'\n\nexport interface OnlyOfficeReqConfig {\n documentServerUrl: string\n config: OnlyOfficeConfig\n}\n\nexport interface OnlyOfficeConvertForm {\n key: string\n url: string\n outputtype: string\n filetype: string\n async: boolean\n token?: string\n}\n\nexport interface OnlyOfficeConfig {\n documentType?: string\n token?: string\n type?: 'mobile' | 'desktop'\n height?: string\n width?: string\n document?: {\n fileType: string\n key: string\n referenceData?: {\n fileKey: string\n instanceId: string\n }\n title: string\n url: string\n info?: {\n owner?: string\n uploaded?: string\n favorite?: boolean\n folder?: string\n sharingSettings?: any[]\n }\n permissions?: {\n /**\n * @deprecated Deprecated since version 5.5, please add the onRequestRestore field instead.\n */\n changeHistory?: boolean\n chat?: boolean\n comment?: boolean\n commentGroups?: any\n copy?: boolean\n deleteCommentAuthorOnly?: boolean\n download?: boolean\n edit?: boolean\n editCommentAuthorOnly?: boolean\n fillForms?: boolean\n modifyContentControl?: boolean\n modifyFilter?: boolean\n print?: boolean\n protect?: boolean\n review?: boolean\n reviewGroups?: string[]\n userInfoGroups?: string[]\n }\n }\n editorConfig?: {\n actionLink?: any\n callbackUrl?: string\n coEditing?: {\n mode: string\n change: boolean\n }\n createUrl?: string\n lang?: string\n mode?: FILE_MODE\n recent?: any[]\n region?: string\n templates?: any[]\n user?: {\n group?: string\n id?: string\n image?: string\n name?: string\n }\n customization?: {\n anonymous?: {\n request?: boolean\n label?: string\n }\n autosave?: boolean\n forcesave?: boolean\n close?: {\n visible: boolean\n text: string\n }\n comments?: boolean\n compactHeader?: boolean\n compactToolbar?: boolean\n compatibleFeatures?: boolean\n customer?: {\n address?: string\n info?: string\n logo?: string\n logoDark?: string\n mail?: string\n name?: string\n phone?: string\n www?: string\n }\n features?: any\n feedback?: any\n goback?: any\n help?: boolean\n hideNotes?: boolean\n hideRightMenu?: boolean\n hideRulers?: boolean\n integrationMode?: string\n logo?: {\n image?: string\n imageDark?: string\n imageLight?: string\n imageEmbedded?: string\n url?: string\n visible?: boolean\n }\n macros?: boolean\n macrosMode?: string\n mentionShare?: boolean\n mobileForceView?: boolean\n plugins?: boolean\n review?: {\n hideReviewDisplay?: boolean\n hoverMode?: boolean\n reviewDisplay?: string\n showReviewChanges?: boolean\n trackChanges?: boolean\n }\n submitForm?: boolean\n toolbarHideFileName?: boolean\n toolbarNoTabs?: boolean\n uiTheme?: string\n unit?: string\n zoom?: number\n about?: boolean\n }\n embedded?: {\n embedUrl?: string\n fullscreenUrl?: string\n saveUrl?: string\n shareUrl?: string\n toolbarDocked?: string\n }\n plugins?: {\n autostart?: string[]\n options?: {\n all?: any\n pluginGuid: any\n }\n pluginsData?: string[]\n }\n }\n events?: {\n onAppReady?: (event: object) => void\n onCollaborativeChanges?: (event: object) => void\n onDocumentReady?: (event: object) => void\n onDocumentStateChange?: (event: { data: boolean }) => void\n onDownloadAs?: (event: object) => void\n onError?: (event: object) => void\n onInfo?: (event: object) => void\n onMetaChange?: (event: object) => void\n onMakeActionLink?: (event: object) => void\n onRequestRefreshFile?: (event: object) => void\n onPluginsReady?: (event: object) => void\n onReady?: (event: object) => void\n onRequestClose?: (event: object) => void\n onRequestCreateNew?: (event: object) => void\n onRequestEditRights?: (event: object) => void\n onRequestHistory?: (event: object) => void\n onRequestHistoryClose?: (event: object) => void\n onRequestHistoryData?: (event: object) => void\n onRequestInsertImage?: (event: object) => void\n onRequestOpen?: (event: object) => void\n onRequestReferenceData?: (event: object) => void\n onRequestReferenceSource?: (event: object) => void\n onRequestRename?: (event: object) => void\n onRequestRestore?: (event: object) => void\n onRequestSaveAs?: (event: object) => void\n onRequestSelectDocument?: (event: object) => void\n onRequestSelectSpreadsheet?: (event: object) => void\n onRequestSendNotify?: (event: object) => void\n onRequestSharingSettings?: (event: object) => void\n onRequestStartFilling?: (event: object) => void\n onRequestUsers?: (event: object) => void\n onSubmit?: (event: object) => void\n onWarning?: (event: object) => void\n }\n}\n\nexport interface OnlyOfficeCallBack {\n /* documentation : https://api.onlyoffice.com/docs/docs-api/usage-api/callback-handler/ */\n key: string // document key\n /*\n status:\n 1 - document is being edited\n 2 - document is ready for saving\n 3 - document saving error has occurred\n 4 - document is closed with no changes\n 6 - document is being edited, but the current document state is saved\n 7 - error has occurred while force saving the document\n */\n status: 1 | 2 | 3 | 4 | 6 | 7\n url?: string // link to download the modified version (for status: 2, 3, 6 or 7)\n notmodified?: boolean // only with status 2\n /*\n actions:\n 0 - the user disconnects from the document co-editing\n 1 - the new user connects to the document co-editing\n 2 - the user clicks the forcesave button.\n */\n actions?: { type: 0 | 1 | 2; userid: string }[]\n forcesavetype?: 0 | 1 | 2 | 3 // The type is present when the status value is equal to 6 or 7 only\n /*\n forcesavetype:\n 0 - to the command service\n 1 - each time the saving is done (e.g. the Save button is clicked), which is only available when the forcesave option is set to true\n 2 - by timer with the settings from the server config\n 3 - each time the form is submitted (e.g. the Complete & Submit button is clicked)\n */\n users?: string[] // when multiple users are editing the document\n}\n"],"names":[],"mappings":"AAAA;;;;CAIC"}
|
|
@@ -108,7 +108,7 @@ describe(_filesmethodsservice.FilesMethods.name, ()=>{
|
|
|
108
108
|
dstDirectory: '../../../foo',
|
|
109
109
|
dstName: '../bar/../'
|
|
110
110
|
};
|
|
111
|
-
expect(()=>(0, _functions.transformAndValidate)(_fileoperationsdto.
|
|
111
|
+
expect(()=>(0, _functions.transformAndValidate)(_fileoperationsdto.CopyMoveFileDto, copyMoveFileDto)).toThrow();
|
|
112
112
|
await expect(filesMethods.copyMove(userTest, spaceEnv, copyMoveFileDto, false)).rejects.toThrow(/is not valid/i);
|
|
113
113
|
});
|
|
114
114
|
it('should avoid path traversal on Compress action', async ()=>{
|
|
@@ -124,9 +124,7 @@ describe(_filesmethodsservice.FilesMethods.name, ()=>{
|
|
|
124
124
|
extension: _compress.tarExtension
|
|
125
125
|
};
|
|
126
126
|
expect(()=>(0, _functions.transformAndValidate)(_fileoperationsdto.CompressFileDto, compressFileDto)).toThrow();
|
|
127
|
-
await expect(filesMethods.compress(userTest, spaceEnv,
|
|
128
|
-
...compressFileDto
|
|
129
|
-
})).rejects.toThrow(/does not exist/i);
|
|
127
|
+
await expect(filesMethods.compress(userTest, spaceEnv, compressFileDto)).rejects.toThrow(/does not exist/i);
|
|
130
128
|
compressFileDto.files[0].path = '../../../bar/../';
|
|
131
129
|
await expect(filesMethods.compress(userTest, spaceEnv, compressFileDto)).rejects.toThrow(/is not valid/i);
|
|
132
130
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../backend/src/applications/files/services/files-methods.service.spec.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Test, TestingModule } from '@nestjs/testing'\nimport path from 'node:path'\nimport { transformAndValidate } from '../../../common/functions'\nimport { Cache } from '../../../infrastructure/cache/services/cache.service'\nimport { ContextManager } from '../../../infrastructure/context/services/context-manager.service'\nimport { DB_TOKEN_PROVIDER } from '../../../infrastructure/database/constants'\nimport { NotificationsManager } from '../../notifications/services/notifications-manager.service'\nimport { SharesManager } from '../../shares/services/shares-manager.service'\nimport { SPACE_REPOSITORY } from '../../spaces/constants/spaces'\nimport { SpaceEnv } from '../../spaces/models/space-env.model'\nimport { SpaceModel } from '../../spaces/models/space.model'\nimport { SpacesManager } from '../../spaces/services/spaces-manager.service'\nimport { SpacesQueries } from '../../spaces/services/spaces-queries.service'\nimport { UserModel } from '../../users/models/user.model'\nimport { UsersQueries } from '../../users/services/users-queries.service'\nimport { generateUserTest } from '../../users/utils/test'\nimport { tarExtension } from '../constants/compress'\nimport { CompressFileDto, CopyMoveFileDto } from '../dto/file-operations.dto'\nimport { FilesManager } from './files-manager.service'\nimport { FilesMethods } from './files-methods.service'\n\ndescribe(FilesMethods.name, () => {\n let filesMethods: FilesMethods\n let spacesManager: SpacesManager\n let userTest: UserModel\n const spaceEnv
|
|
1
|
+
{"version":3,"sources":["../../../../../backend/src/applications/files/services/files-methods.service.spec.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Test, TestingModule } from '@nestjs/testing'\nimport path from 'node:path'\nimport { transformAndValidate } from '../../../common/functions'\nimport { Cache } from '../../../infrastructure/cache/services/cache.service'\nimport { ContextManager } from '../../../infrastructure/context/services/context-manager.service'\nimport { DB_TOKEN_PROVIDER } from '../../../infrastructure/database/constants'\nimport { NotificationsManager } from '../../notifications/services/notifications-manager.service'\nimport { SharesManager } from '../../shares/services/shares-manager.service'\nimport { SPACE_REPOSITORY } from '../../spaces/constants/spaces'\nimport { SpaceEnv } from '../../spaces/models/space-env.model'\nimport { SpaceModel } from '../../spaces/models/space.model'\nimport { SpacesManager } from '../../spaces/services/spaces-manager.service'\nimport { SpacesQueries } from '../../spaces/services/spaces-queries.service'\nimport { UserModel } from '../../users/models/user.model'\nimport { UsersQueries } from '../../users/services/users-queries.service'\nimport { generateUserTest } from '../../users/utils/test'\nimport { tarExtension } from '../constants/compress'\nimport { CompressFileDto, CopyMoveFileDto } from '../dto/file-operations.dto'\nimport { FilesManager } from './files-manager.service'\nimport { FilesMethods } from './files-methods.service'\n\ndescribe(FilesMethods.name, () => {\n let filesMethods: FilesMethods\n let spacesManager: SpacesManager\n let userTest: UserModel\n const spaceEnv = {\n id: 1,\n alias: 'project',\n name: 'project',\n enabled: true,\n permissions: 'a:d:m:so',\n role: 0,\n realBasePath: SpaceModel.getFilesPath('project'),\n realPath: path.join(SpaceModel.getFilesPath('project'), 'foo'),\n url: `${SPACE_REPOSITORY.FILES}/project/foo`\n } as SpaceEnv\n\n beforeAll(async () => {\n const module: TestingModule = await Test.createTestingModule({\n imports: [],\n providers: [\n FilesMethods,\n SpacesManager,\n { provide: DB_TOKEN_PROVIDER, useValue: {} },\n {\n provide: Cache,\n useValue: { get: () => null }\n },\n { provide: ContextManager, useValue: {} },\n {\n provide: NotificationsManager,\n useValue: {}\n },\n { provide: UsersQueries, useValue: {} },\n { provide: SharesManager, useValue: {} },\n {\n provide: SpacesQueries,\n useValue: {\n permissions: () => spaceEnv\n }\n },\n { provide: FilesManager, useValue: {} }\n ]\n }).compile()\n\n module.useLogger(['fatal'])\n filesMethods = module.get<FilesMethods>(FilesMethods)\n spacesManager = module.get<SpacesManager>(SpacesManager)\n userTest = new UserModel(generateUserTest())\n // mock\n spacesManager.updateSpacesQuota = jest.fn().mockReturnValue(undefined)\n })\n\n it('should be defined', () => {\n expect(filesMethods).toBeDefined()\n expect(spacesManager).toBeDefined()\n expect(userTest).toBeDefined()\n })\n\n it('should avoid path traversal on CopyMove action', async () => {\n const copyMoveFileDto: CopyMoveFileDto = { dstDirectory: '../../../foo', dstName: '../bar/../' }\n expect(() => transformAndValidate(CopyMoveFileDto, copyMoveFileDto)).toThrow()\n await expect((filesMethods as any).copyMove(userTest, spaceEnv, copyMoveFileDto, false)).rejects.toThrow(/is not valid/i)\n })\n\n it('should avoid path traversal on Compress action', async () => {\n const compressFileDto: CompressFileDto = {\n name: '../../archive',\n compressInDirectory: false,\n files: [{ name: '../../foo', rootAlias: undefined }],\n extension: tarExtension\n }\n expect(() => transformAndValidate(CompressFileDto, compressFileDto)).toThrow()\n await expect(filesMethods.compress(userTest, spaceEnv, compressFileDto)).rejects.toThrow(/does not exist/i)\n compressFileDto.files[0].path = '../../../bar/../'\n await expect(filesMethods.compress(userTest, spaceEnv, compressFileDto)).rejects.toThrow(/is not valid/i)\n })\n})\n"],"names":["describe","FilesMethods","name","filesMethods","spacesManager","userTest","spaceEnv","id","alias","enabled","permissions","role","realBasePath","SpaceModel","getFilesPath","realPath","path","join","url","SPACE_REPOSITORY","FILES","beforeAll","module","Test","createTestingModule","imports","providers","SpacesManager","provide","DB_TOKEN_PROVIDER","useValue","Cache","get","ContextManager","NotificationsManager","UsersQueries","SharesManager","SpacesQueries","FilesManager","compile","useLogger","UserModel","generateUserTest","updateSpacesQuota","jest","fn","mockReturnValue","undefined","it","expect","toBeDefined","copyMoveFileDto","dstDirectory","dstName","transformAndValidate","CopyMoveFileDto","toThrow","copyMove","rejects","compressFileDto","compressInDirectory","files","rootAlias","extension","tarExtension","CompressFileDto","compress"],"mappings":"AAAA;;;;CAIC;;;;yBAEmC;iEACnB;2BACoB;8BACf;uCACS;2BACG;6CACG;sCACP;wBACG;4BAEN;sCACG;sCACA;2BACJ;qCACG;sBACI;0BACJ;mCACoB;qCACpB;qCACA;;;;;;AAE7BA,SAASC,iCAAY,CAACC,IAAI,EAAE;IAC1B,IAAIC;IACJ,IAAIC;IACJ,IAAIC;IACJ,MAAMC,WAAW;QACfC,IAAI;QACJC,OAAO;QACPN,MAAM;QACNO,SAAS;QACTC,aAAa;QACbC,MAAM;QACNC,cAAcC,sBAAU,CAACC,YAAY,CAAC;QACtCC,UAAUC,iBAAI,CAACC,IAAI,CAACJ,sBAAU,CAACC,YAAY,CAAC,YAAY;QACxDI,KAAK,GAAGC,wBAAgB,CAACC,KAAK,CAAC,YAAY,CAAC;IAC9C;IAEAC,UAAU;QACR,MAAMC,SAAwB,MAAMC,aAAI,CAACC,mBAAmB,CAAC;YAC3DC,SAAS,EAAE;YACXC,WAAW;gBACTzB,iCAAY;gBACZ0B,mCAAa;gBACb;oBAAEC,SAASC,4BAAiB;oBAAEC,UAAU,CAAC;gBAAE;gBAC3C;oBACEF,SAASG,mBAAK;oBACdD,UAAU;wBAAEE,KAAK,IAAM;oBAAK;gBAC9B;gBACA;oBAAEJ,SAASK,qCAAc;oBAAEH,UAAU,CAAC;gBAAE;gBACxC;oBACEF,SAASM,iDAAoB;oBAC7BJ,UAAU,CAAC;gBACb;gBACA;oBAAEF,SAASO,iCAAY;oBAAEL,UAAU,CAAC;gBAAE;gBACtC;oBAAEF,SAASQ,mCAAa;oBAAEN,UAAU,CAAC;gBAAE;gBACvC;oBACEF,SAASS,mCAAa;oBACtBP,UAAU;wBACRpB,aAAa,IAAMJ;oBACrB;gBACF;gBACA;oBAAEsB,SAASU,iCAAY;oBAAER,UAAU,CAAC;gBAAE;aACvC;QACH,GAAGS,OAAO;QAEVjB,OAAOkB,SAAS,CAAC;YAAC;SAAQ;QAC1BrC,eAAemB,OAAOU,GAAG,CAAe/B,iCAAY;QACpDG,gBAAgBkB,OAAOU,GAAG,CAAgBL,mCAAa;QACvDtB,WAAW,IAAIoC,oBAAS,CAACC,IAAAA,sBAAgB;QACzC,OAAO;QACPtC,cAAcuC,iBAAiB,GAAGC,KAAKC,EAAE,GAAGC,eAAe,CAACC;IAC9D;IAEAC,GAAG,qBAAqB;QACtBC,OAAO9C,cAAc+C,WAAW;QAChCD,OAAO7C,eAAe8C,WAAW;QACjCD,OAAO5C,UAAU6C,WAAW;IAC9B;IAEAF,GAAG,kDAAkD;QACnD,MAAMG,kBAAmC;YAAEC,cAAc;YAAgBC,SAAS;QAAa;QAC/FJ,OAAO,IAAMK,IAAAA,+BAAoB,EAACC,kCAAe,EAAEJ,kBAAkBK,OAAO;QAC5E,MAAMP,OAAO,AAAC9C,aAAqBsD,QAAQ,CAACpD,UAAUC,UAAU6C,iBAAiB,QAAQO,OAAO,CAACF,OAAO,CAAC;IAC3G;IAEAR,GAAG,kDAAkD;QACnD,MAAMW,kBAAmC;YACvCzD,MAAM;YACN0D,qBAAqB;YACrBC,OAAO;gBAAC;oBAAE3D,MAAM;oBAAa4D,WAAWf;gBAAU;aAAE;YACpDgB,WAAWC,sBAAY;QACzB;QACAf,OAAO,IAAMK,IAAAA,+BAAoB,EAACW,kCAAe,EAAEN,kBAAkBH,OAAO;QAC5E,MAAMP,OAAO9C,aAAa+D,QAAQ,CAAC7D,UAAUC,UAAUqD,kBAAkBD,OAAO,CAACF,OAAO,CAAC;QACzFG,gBAAgBE,KAAK,CAAC,EAAE,CAAC7C,IAAI,GAAG;QAChC,MAAMiC,OAAO9C,aAAa+D,QAAQ,CAAC7D,UAAUC,UAAUqD,kBAAkBD,OAAO,CAACF,OAAO,CAAC;IAC3F;AACF"}
|
|
@@ -36,6 +36,10 @@ let FilesRecents = class FilesRecents {
|
|
|
36
36
|
return this.filesQueries.getRecentsFromUser(user.id, spaceIds, shareIds, limit);
|
|
37
37
|
}
|
|
38
38
|
async updateRecents(user, space, files) {
|
|
39
|
+
if (space.inTrashRepository) {
|
|
40
|
+
// Ignore trashed files
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
39
43
|
const timestamp = (0, _shared.currentTimeStamp)(null, true) - (0, _functions.convertHumanTimeToMs)(this.keepTime);
|
|
40
44
|
const location = this.getLocation(user, space, files);
|
|
41
45
|
// only store files, ignore dirs
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../backend/src/applications/files/services/files-recents.service.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Injectable } from '@nestjs/common'\nimport { convertDiffUpdate, convertHumanTimeToMs, diffCollection } from '../../../common/functions'\nimport { currentTimeStamp } from '../../../common/shared'\nimport { SharesQueries } from '../../shares/services/shares-queries.service'\nimport { SpaceEnv } from '../../spaces/models/space-env.model'\nimport { SpacesQueries } from '../../spaces/services/spaces-queries.service'\nimport { UserModel } from '../../users/models/user.model'\nimport { FileProps } from '../interfaces/file-props.interface'\nimport { FileRecentLocation } from '../interfaces/file-recent-location.interface'\nimport { FileRecent } from '../schemas/file-recent.interface'\nimport { FilesQueries } from './files-queries.service'\n\n@Injectable()\nexport class FilesRecents {\n private readonly keepTime = '14d'\n\n constructor(\n private readonly filesQueries: FilesQueries,\n private readonly spacesQueries: SpacesQueries,\n private readonly sharesQueries: SharesQueries\n ) {}\n\n async getRecents(user: UserModel, limit: number): Promise<FileRecent[]> {\n const [spaceIds, shareIds] = await Promise.all([this.spacesQueries.spaceIds(user.id), this.sharesQueries.shareIds(user.id, +user.isAdmin)])\n return this.filesQueries.getRecentsFromUser(user.id, spaceIds, shareIds, limit)\n }\n\n async updateRecents(user: UserModel, space: SpaceEnv, files: FileProps[]): Promise<void> {\n const timestamp = currentTimeStamp(null, true) - convertHumanTimeToMs(this.keepTime)\n const location = this.getLocation(user, space, files)\n // only store files, ignore dirs\n const fsRecents = files.filter((f) => !f.isDir && f.size > 0 && f.mtime > timestamp)\n const dbRecents = await this.filesQueries.getRecentsFromLocation(location)\n if (!fsRecents.length && !dbRecents.length) {\n return\n }\n const [add, update, remove] = diffCollection(dbRecents as any, fsRecents as any, ['mtime', 'name'])\n const toAdd: Partial<FileRecent>[] = add.map(\n (f: FileProps): Partial<FileRecent> =>\n ({\n id: f.id,\n name: f.name,\n mtime: f.mtime,\n mime: f.mime,\n ...location,\n ...(space.inSharesList && { shareId: f.root.id })\n }) as FileRecent\n )\n const toUpdate: Record<string | 'object', Partial<FileProps> | FileProps>[] = convertDiffUpdate(update)\n const toRemove: number[] = remove.map((f: FileRecent) => f.id)\n await this.filesQueries.updateRecents(location, toAdd, toUpdate, toRemove)\n }\n\n private getLocation(user: UserModel, space: SpaceEnv, files: FileProps[]): FileRecentLocation {\n const location: FileRecentLocation = { path: space.url }\n if (space.inPersonalSpace) {\n location.ownerId = user.id\n } else if (space.inSharesList) {\n location.shareId = files.map((f) => f.root.id)\n } else if (space.inSharesRepository) {\n location.shareId = space.id\n } else {\n location.spaceId = space.id\n }\n return location\n }\n}\n"],"names":["FilesRecents","getRecents","user","limit","spaceIds","shareIds","Promise","all","spacesQueries","id","sharesQueries","isAdmin","filesQueries","getRecentsFromUser","updateRecents","space","files","timestamp","currentTimeStamp","convertHumanTimeToMs","keepTime","location","getLocation","fsRecents","filter","f","isDir","size","mtime","dbRecents","getRecentsFromLocation","length","add","update","remove","diffCollection","toAdd","map","name","mime","inSharesList","shareId","root","toUpdate","convertDiffUpdate","toRemove","path","url","inPersonalSpace","ownerId","inSharesRepository","spaceId"],"mappings":"AAAA;;;;CAIC;;;;+BAeYA;;;eAAAA;;;wBAbc;2BAC6C;wBACvC;sCACH;sCAEA;qCAKD;;;;;;;;;;AAGtB,IAAA,AAAMA,eAAN,MAAMA;IASX,MAAMC,WAAWC,IAAe,EAAEC,KAAa,EAAyB;QACtE,MAAM,CAACC,UAAUC,SAAS,GAAG,MAAMC,QAAQC,GAAG,CAAC;YAAC,IAAI,CAACC,aAAa,CAACJ,QAAQ,CAACF,KAAKO,EAAE;YAAG,IAAI,CAACC,aAAa,CAACL,QAAQ,CAACH,KAAKO,EAAE,EAAE,CAACP,KAAKS,OAAO;SAAE;QAC1I,OAAO,IAAI,CAACC,YAAY,CAACC,kBAAkB,CAACX,KAAKO,EAAE,EAAEL,UAAUC,UAAUF;IAC3E;IAEA,MAAMW,cAAcZ,IAAe,EAAEa,KAAe,EAAEC,KAAkB,EAAiB;QACvF,MAAMC,YAAYC,IAAAA,wBAAgB,EAAC,MAAM,QAAQC,IAAAA,+BAAoB,EAAC,IAAI,CAACC,QAAQ;QACnF,MAAMC,WAAW,IAAI,CAACC,WAAW,
|
|
1
|
+
{"version":3,"sources":["../../../../../backend/src/applications/files/services/files-recents.service.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Injectable } from '@nestjs/common'\nimport { convertDiffUpdate, convertHumanTimeToMs, diffCollection } from '../../../common/functions'\nimport { currentTimeStamp } from '../../../common/shared'\nimport { SharesQueries } from '../../shares/services/shares-queries.service'\nimport { SpaceEnv } from '../../spaces/models/space-env.model'\nimport { SpacesQueries } from '../../spaces/services/spaces-queries.service'\nimport { UserModel } from '../../users/models/user.model'\nimport { FileProps } from '../interfaces/file-props.interface'\nimport { FileRecentLocation } from '../interfaces/file-recent-location.interface'\nimport { FileRecent } from '../schemas/file-recent.interface'\nimport { FilesQueries } from './files-queries.service'\n\n@Injectable()\nexport class FilesRecents {\n private readonly keepTime = '14d'\n\n constructor(\n private readonly filesQueries: FilesQueries,\n private readonly spacesQueries: SpacesQueries,\n private readonly sharesQueries: SharesQueries\n ) {}\n\n async getRecents(user: UserModel, limit: number): Promise<FileRecent[]> {\n const [spaceIds, shareIds] = await Promise.all([this.spacesQueries.spaceIds(user.id), this.sharesQueries.shareIds(user.id, +user.isAdmin)])\n return this.filesQueries.getRecentsFromUser(user.id, spaceIds, shareIds, limit)\n }\n\n async updateRecents(user: UserModel, space: SpaceEnv, files: FileProps[]): Promise<void> {\n if (space.inTrashRepository) {\n // Ignore trashed files\n return\n }\n const timestamp = currentTimeStamp(null, true) - convertHumanTimeToMs(this.keepTime)\n const location = this.getLocation(user, space, files)\n // only store files, ignore dirs\n const fsRecents = files.filter((f) => !f.isDir && f.size > 0 && f.mtime > timestamp)\n const dbRecents = await this.filesQueries.getRecentsFromLocation(location)\n if (!fsRecents.length && !dbRecents.length) {\n return\n }\n const [add, update, remove] = diffCollection(dbRecents as any, fsRecents as any, ['mtime', 'name'])\n const toAdd: Partial<FileRecent>[] = add.map(\n (f: FileProps): Partial<FileRecent> =>\n ({\n id: f.id,\n name: f.name,\n mtime: f.mtime,\n mime: f.mime,\n ...location,\n ...(space.inSharesList && { shareId: f.root.id })\n }) as FileRecent\n )\n const toUpdate: Record<string | 'object', Partial<FileProps> | FileProps>[] = convertDiffUpdate(update)\n const toRemove: number[] = remove.map((f: FileRecent) => f.id)\n await this.filesQueries.updateRecents(location, toAdd, toUpdate, toRemove)\n }\n\n private getLocation(user: UserModel, space: SpaceEnv, files: FileProps[]): FileRecentLocation {\n const location: FileRecentLocation = { path: space.url }\n if (space.inPersonalSpace) {\n location.ownerId = user.id\n } else if (space.inSharesList) {\n location.shareId = files.map((f) => f.root.id)\n } else if (space.inSharesRepository) {\n location.shareId = space.id\n } else {\n location.spaceId = space.id\n }\n return location\n }\n}\n"],"names":["FilesRecents","getRecents","user","limit","spaceIds","shareIds","Promise","all","spacesQueries","id","sharesQueries","isAdmin","filesQueries","getRecentsFromUser","updateRecents","space","files","inTrashRepository","timestamp","currentTimeStamp","convertHumanTimeToMs","keepTime","location","getLocation","fsRecents","filter","f","isDir","size","mtime","dbRecents","getRecentsFromLocation","length","add","update","remove","diffCollection","toAdd","map","name","mime","inSharesList","shareId","root","toUpdate","convertDiffUpdate","toRemove","path","url","inPersonalSpace","ownerId","inSharesRepository","spaceId"],"mappings":"AAAA;;;;CAIC;;;;+BAeYA;;;eAAAA;;;wBAbc;2BAC6C;wBACvC;sCACH;sCAEA;qCAKD;;;;;;;;;;AAGtB,IAAA,AAAMA,eAAN,MAAMA;IASX,MAAMC,WAAWC,IAAe,EAAEC,KAAa,EAAyB;QACtE,MAAM,CAACC,UAAUC,SAAS,GAAG,MAAMC,QAAQC,GAAG,CAAC;YAAC,IAAI,CAACC,aAAa,CAACJ,QAAQ,CAACF,KAAKO,EAAE;YAAG,IAAI,CAACC,aAAa,CAACL,QAAQ,CAACH,KAAKO,EAAE,EAAE,CAACP,KAAKS,OAAO;SAAE;QAC1I,OAAO,IAAI,CAACC,YAAY,CAACC,kBAAkB,CAACX,KAAKO,EAAE,EAAEL,UAAUC,UAAUF;IAC3E;IAEA,MAAMW,cAAcZ,IAAe,EAAEa,KAAe,EAAEC,KAAkB,EAAiB;QACvF,IAAID,MAAME,iBAAiB,EAAE;YAC3B,uBAAuB;YACvB;QACF;QACA,MAAMC,YAAYC,IAAAA,wBAAgB,EAAC,MAAM,QAAQC,IAAAA,+BAAoB,EAAC,IAAI,CAACC,QAAQ;QACnF,MAAMC,WAAW,IAAI,CAACC,WAAW,CAACrB,MAAMa,OAAOC;QAC/C,gCAAgC;QAChC,MAAMQ,YAAYR,MAAMS,MAAM,CAAC,CAACC,IAAM,CAACA,EAAEC,KAAK,IAAID,EAAEE,IAAI,GAAG,KAAKF,EAAEG,KAAK,GAAGX;QAC1E,MAAMY,YAAY,MAAM,IAAI,CAAClB,YAAY,CAACmB,sBAAsB,CAACT;QACjE,IAAI,CAACE,UAAUQ,MAAM,IAAI,CAACF,UAAUE,MAAM,EAAE;YAC1C;QACF;QACA,MAAM,CAACC,KAAKC,QAAQC,OAAO,GAAGC,IAAAA,yBAAc,EAACN,WAAkBN,WAAkB;YAAC;YAAS;SAAO;QAClG,MAAMa,QAA+BJ,IAAIK,GAAG,CAC1C,CAACZ,IACE,CAAA;gBACCjB,IAAIiB,EAAEjB,EAAE;gBACR8B,MAAMb,EAAEa,IAAI;gBACZV,OAAOH,EAAEG,KAAK;gBACdW,MAAMd,EAAEc,IAAI;gBACZ,GAAGlB,QAAQ;gBACX,GAAIP,MAAM0B,YAAY,IAAI;oBAAEC,SAAShB,EAAEiB,IAAI,CAAClC,EAAE;gBAAC,CAAC;YAClD,CAAA;QAEJ,MAAMmC,WAAwEC,IAAAA,4BAAiB,EAACX;QAChG,MAAMY,WAAqBX,OAAOG,GAAG,CAAC,CAACZ,IAAkBA,EAAEjB,EAAE;QAC7D,MAAM,IAAI,CAACG,YAAY,CAACE,aAAa,CAACQ,UAAUe,OAAOO,UAAUE;IACnE;IAEQvB,YAAYrB,IAAe,EAAEa,KAAe,EAAEC,KAAkB,EAAsB;QAC5F,MAAMM,WAA+B;YAAEyB,MAAMhC,MAAMiC,GAAG;QAAC;QACvD,IAAIjC,MAAMkC,eAAe,EAAE;YACzB3B,SAAS4B,OAAO,GAAGhD,KAAKO,EAAE;QAC5B,OAAO,IAAIM,MAAM0B,YAAY,EAAE;YAC7BnB,SAASoB,OAAO,GAAG1B,MAAMsB,GAAG,CAAC,CAACZ,IAAMA,EAAEiB,IAAI,CAAClC,EAAE;QAC/C,OAAO,IAAIM,MAAMoC,kBAAkB,EAAE;YACnC7B,SAASoB,OAAO,GAAG3B,MAAMN,EAAE;QAC7B,OAAO;YACLa,SAAS8B,OAAO,GAAGrC,MAAMN,EAAE;QAC7B;QACA,OAAOa;IACT;IArDA,YACE,AAAiBV,YAA0B,EAC3C,AAAiBJ,aAA4B,EAC7C,AAAiBE,aAA4B,CAC7C;aAHiBE,eAAAA;aACAJ,gBAAAA;aACAE,gBAAAA;aALFW,WAAW;IAMzB;AAkDL"}
|
|
@@ -53,6 +53,21 @@ function _ts_param(paramIndex, decorator) {
|
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
55
|
let FilesScheduler = class FilesScheduler {
|
|
56
|
+
async onStartup() {
|
|
57
|
+
try {
|
|
58
|
+
await this.cleanupInterruptedTasks();
|
|
59
|
+
await this.clearRecentFiles();
|
|
60
|
+
} catch (e) {
|
|
61
|
+
this.logger.error(e);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async afterStartup() {
|
|
65
|
+
try {
|
|
66
|
+
await this.indexContentFiles();
|
|
67
|
+
} catch (e) {
|
|
68
|
+
this.logger.error(e);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
56
71
|
async cleanupInterruptedTasks() {
|
|
57
72
|
this.logger.log(`${this.cleanupInterruptedTasks.name} - START`);
|
|
58
73
|
try {
|
|
@@ -182,11 +197,17 @@ let FilesScheduler = class FilesScheduler {
|
|
|
182
197
|
}
|
|
183
198
|
};
|
|
184
199
|
_ts_decorate([
|
|
185
|
-
(0, _schedule.Timeout)(
|
|
200
|
+
(0, _schedule.Timeout)(30_000),
|
|
201
|
+
_ts_metadata("design:type", Function),
|
|
202
|
+
_ts_metadata("design:paramtypes", []),
|
|
203
|
+
_ts_metadata("design:returntype", Promise)
|
|
204
|
+
], FilesScheduler.prototype, "onStartup", null);
|
|
205
|
+
_ts_decorate([
|
|
206
|
+
(0, _schedule.Timeout)(180_000),
|
|
186
207
|
_ts_metadata("design:type", Function),
|
|
187
208
|
_ts_metadata("design:paramtypes", []),
|
|
188
209
|
_ts_metadata("design:returntype", Promise)
|
|
189
|
-
], FilesScheduler.prototype, "
|
|
210
|
+
], FilesScheduler.prototype, "afterStartup", null);
|
|
190
211
|
_ts_decorate([
|
|
191
212
|
(0, _schedule.Cron)(_schedule.CronExpression.EVERY_DAY_AT_MIDNIGHT),
|
|
192
213
|
_ts_metadata("design:type", Function),
|
|
@@ -194,14 +215,12 @@ _ts_decorate([
|
|
|
194
215
|
_ts_metadata("design:returntype", Promise)
|
|
195
216
|
], FilesScheduler.prototype, "cleanupUserTaskFiles", null);
|
|
196
217
|
_ts_decorate([
|
|
197
|
-
(0, _schedule.Timeout)(30000),
|
|
198
218
|
(0, _schedule.Cron)(_schedule.CronExpression.EVERY_8_HOURS),
|
|
199
219
|
_ts_metadata("design:type", Function),
|
|
200
220
|
_ts_metadata("design:paramtypes", []),
|
|
201
221
|
_ts_metadata("design:returntype", Promise)
|
|
202
222
|
], FilesScheduler.prototype, "clearRecentFiles", null);
|
|
203
223
|
_ts_decorate([
|
|
204
|
-
(0, _schedule.Timeout)(120000),
|
|
205
224
|
(0, _schedule.Cron)(_schedule.CronExpression.EVERY_4_HOURS),
|
|
206
225
|
_ts_metadata("design:type", Function),
|
|
207
226
|
_ts_metadata("design:paramtypes", []),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../backend/src/applications/files/services/files-scheduler.service.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Inject, Injectable, Logger } from '@nestjs/common'\nimport { Cron, CronExpression, Timeout } from '@nestjs/schedule'\nimport { isNotNull, sql } from 'drizzle-orm'\nimport { unionAll } from 'drizzle-orm/mysql-core'\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\nimport { configuration } from '../../../configuration/config.environment'\nimport { Cache } from '../../../infrastructure/cache/services/cache.service'\nimport { DB_TOKEN_PROVIDER } from '../../../infrastructure/database/constants'\nimport { DBSchema } from '../../../infrastructure/database/interfaces/database.interface'\nimport { getTablesWithFileIdColumn } from '../../../infrastructure/database/utils'\nimport { USER_ROLE } from '../../users/constants/user'\nimport { UserModel } from '../../users/models/user.model'\nimport { users } from '../../users/schemas/users.schema'\nimport { CACHE_TASK_PREFIX } from '../constants/cache'\nimport { FileTask, FileTaskStatus } from '../models/file-task'\nimport { filesRecents } from '../schemas/files-recents.schema'\nimport { files } from '../schemas/files.schema'\nimport { dirHasChildren, isPathExists, removeFiles } from '../utils/files'\nimport { FilesContentManager } from './files-content-manager.service'\nimport { FilesTasksManager } from './files-tasks-manager.service'\n\n@Injectable()\nexport class FilesScheduler {\n private readonly logger = new Logger(FilesScheduler.name)\n\n constructor(\n @Inject(DB_TOKEN_PROVIDER) private readonly db: DBSchema,\n private readonly cache: Cache,\n private readonly filesContentManager: FilesContentManager\n ) {}\n\n @Timeout(30000)\n async cleanupInterruptedTasks(): Promise<void> {\n this.logger.log(`${this.cleanupInterruptedTasks.name} - START`)\n try {\n let nb = 0\n const keys = await this.cache.keys(`${CACHE_TASK_PREFIX}-*`)\n for (const key of keys) {\n const task = await this.cache.get(key)\n if (task && task.status === FileTaskStatus.PENDING) {\n task.status = FileTaskStatus.ERROR\n task.result = 'Interrupted'\n nb++\n this.cache.set(key, task).catch((e: Error) => this.logger.error(`${this.cleanupInterruptedTasks.name} - ${e}`))\n }\n }\n this.logger.log(`${this.cleanupInterruptedTasks.name} - ${nb} tasks cleaned : END`)\n } catch (e) {\n this.logger.error(`${this.cleanupInterruptedTasks.name} - ${e}`)\n }\n }\n\n @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)\n async cleanupUserTaskFiles(): Promise<void> {\n this.logger.log(`${this.cleanupUserTaskFiles.name} - START`)\n try {\n for (const user of await this.db\n .select({\n id: users.id,\n login: users.login,\n role: users.role\n })\n .from(users)) {\n const userTasksPath = UserModel.getTasksPath(user.login, user.role === USER_ROLE.GUEST, user.role === USER_ROLE.LINK)\n if (!(await isPathExists(userTasksPath))) {\n continue\n }\n if (await dirHasChildren(userTasksPath, false)) {\n const cacheKey = FilesTasksManager.getCacheKey(user.id)\n const keys = await this.cache.keys(cacheKey)\n const excludeFiles = (await this.cache.mget(keys))\n .filter((task: FileTask) => task && task.status === FileTaskStatus.PENDING && task.props.compressInDirectory === false)\n .map((task: FileTask) => task.name)\n for (const f of (await fs.readdir(userTasksPath)).filter((f: string) => excludeFiles.indexOf(f) === -1)) {\n try {\n removeFiles(path.join(userTasksPath, f)).catch((e: Error) => this.logger.error(`${this.cleanupUserTaskFiles.name} - ${e}`))\n } catch (e) {\n this.logger.error(`${this.cleanupUserTaskFiles.name} - unable to remove ${path.join(userTasksPath, f)} : ${e}`)\n }\n }\n }\n }\n } catch (e) {\n this.logger.error(`${this.cleanupUserTaskFiles.name} - ${e}`)\n }\n this.logger.log(`${this.cleanupUserTaskFiles.name} - END`)\n }\n\n @Timeout(30000)\n @Cron(CronExpression.EVERY_8_HOURS)\n async clearRecentFiles(): Promise<void> {\n this.logger.log(`${this.clearRecentFiles.name} - START`)\n const keepNumber = 100\n let nbCleared = 0\n try {\n for (const fk of [filesRecents.ownerId, filesRecents.spaceId, filesRecents.shareId]) {\n const [r] = await this.db.execute(sql`\n DELETE\n FROM ${filesRecents}\n WHERE ${fk} IS NOT NULL\n AND id NOT IN (SELECT id\n FROM (SELECT id,\n ROW_NUMBER() OVER (PARTITION BY ${fk} ORDER BY ${filesRecents.mtime}) AS rn\n FROM ${filesRecents}\n WHERE ${fk} IS NOT NULL) AS ranked\n WHERE ranked.rn <= ${keepNumber})\n `)\n nbCleared += r.affectedRows\n }\n } catch (e) {\n this.logger.error(`${this.clearRecentFiles.name} - ${e}`)\n }\n this.logger.log(`${this.clearRecentFiles.name} - ${nbCleared} records cleared - END`)\n }\n\n @Timeout(120000)\n @Cron(CronExpression.EVERY_4_HOURS)\n async indexContentFiles(): Promise<void> {\n // Conditional loading of file content indexing\n if (!configuration.applications.files.contentIndexing) return\n this.logger.log(`${this.indexContentFiles.name} - START`)\n await this.filesContentManager.parseAndIndexAllFiles()\n this.logger.log(`${this.indexContentFiles.name} - END`)\n }\n\n @Cron(CronExpression.EVERY_DAY_AT_4AM)\n async deleteOrphanFiles() {\n this.logger.log(`${this.deleteOrphanFiles.name} - START`)\n const selects: any[] = []\n for (const table of getTablesWithFileIdColumn()) {\n selects.push(this.db.selectDistinct({ id: table.fileId }).from(table).where(isNotNull(table.fileId)))\n }\n if (selects.length === 0) {\n this.logger.warn(`${this.deleteOrphanFiles.name} - no tables with fileId column`)\n return\n }\n const unionSub = (selects.length === 1 ? selects[0] : unionAll(...(selects as [any, any, ...any[]]))).as('u')\n // Debug\n // const [preview] = (await this.db.execute(sql`\n // SELECT f.id\n // FROM ${files} AS f\n // LEFT JOIN ${unionSub} ON ${unionSub.id} = f.id\n // WHERE ${unionSub.id} IS NULL\n // `)) as any[]\n // console.log(preview.length, preview)\n const deleteQuery = sql`\n DELETE f\n FROM ${files} AS f\n LEFT JOIN ${unionSub} ON ${unionSub.id} = f.id\n WHERE ${unionSub.id} IS NULL\n `\n try {\n await this.db.transaction(async (tx) => {\n const [r] = await tx.execute(deleteQuery)\n this.logger.log(`${this.deleteOrphanFiles.name} - files: ${r.affectedRows}`)\n })\n } catch (e) {\n this.logger.log(`${this.deleteOrphanFiles.name} - ${e}`)\n }\n this.logger.log(`${this.deleteOrphanFiles.name} - END`)\n }\n}\n"],"names":["FilesScheduler","cleanupInterruptedTasks","logger","log","name","nb","keys","cache","CACHE_TASK_PREFIX","key","task","get","status","FileTaskStatus","PENDING","ERROR","result","set","catch","e","error","cleanupUserTaskFiles","user","db","select","id","users","login","role","from","userTasksPath","UserModel","getTasksPath","USER_ROLE","GUEST","LINK","isPathExists","dirHasChildren","cacheKey","FilesTasksManager","getCacheKey","excludeFiles","mget","filter","props","compressInDirectory","map","f","fs","readdir","indexOf","removeFiles","path","join","clearRecentFiles","keepNumber","nbCleared","fk","filesRecents","ownerId","spaceId","shareId","r","execute","sql","mtime","affectedRows","indexContentFiles","configuration","applications","files","contentIndexing","filesContentManager","parseAndIndexAllFiles","deleteOrphanFiles","selects","table","getTablesWithFileIdColumn","push","selectDistinct","fileId","where","isNotNull","length","warn","unionSub","unionAll","as","deleteQuery","transaction","tx","Logger","EVERY_DAY_AT_MIDNIGHT","EVERY_8_HOURS","EVERY_4_HOURS","EVERY_DAY_AT_4AM"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../backend/src/applications/files/services/files-scheduler.service.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Inject, Injectable, Logger } from '@nestjs/common'\nimport { Cron, CronExpression, Timeout } from '@nestjs/schedule'\nimport { isNotNull, sql } from 'drizzle-orm'\nimport { unionAll } from 'drizzle-orm/mysql-core'\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\nimport { configuration } from '../../../configuration/config.environment'\nimport { Cache } from '../../../infrastructure/cache/services/cache.service'\nimport { DB_TOKEN_PROVIDER } from '../../../infrastructure/database/constants'\nimport { DBSchema } from '../../../infrastructure/database/interfaces/database.interface'\nimport { getTablesWithFileIdColumn } from '../../../infrastructure/database/utils'\nimport { USER_ROLE } from '../../users/constants/user'\nimport { UserModel } from '../../users/models/user.model'\nimport { users } from '../../users/schemas/users.schema'\nimport { CACHE_TASK_PREFIX } from '../constants/cache'\nimport { FileTask, FileTaskStatus } from '../models/file-task'\nimport { filesRecents } from '../schemas/files-recents.schema'\nimport { files } from '../schemas/files.schema'\nimport { dirHasChildren, isPathExists, removeFiles } from '../utils/files'\nimport { FilesContentManager } from './files-content-manager.service'\nimport { FilesTasksManager } from './files-tasks-manager.service'\n\n@Injectable()\nexport class FilesScheduler {\n private readonly logger = new Logger(FilesScheduler.name)\n\n constructor(\n @Inject(DB_TOKEN_PROVIDER) private readonly db: DBSchema,\n private readonly cache: Cache,\n private readonly filesContentManager: FilesContentManager\n ) {}\n\n @Timeout(30_000)\n async onStartup(): Promise<void> {\n try {\n await this.cleanupInterruptedTasks()\n await this.clearRecentFiles()\n } catch (e) {\n this.logger.error(e)\n }\n }\n\n @Timeout(180_000)\n async afterStartup(): Promise<void> {\n try {\n await this.indexContentFiles()\n } catch (e) {\n this.logger.error(e)\n }\n }\n\n async cleanupInterruptedTasks(): Promise<void> {\n this.logger.log(`${this.cleanupInterruptedTasks.name} - START`)\n try {\n let nb = 0\n const keys = await this.cache.keys(`${CACHE_TASK_PREFIX}-*`)\n for (const key of keys) {\n const task = await this.cache.get(key)\n if (task && task.status === FileTaskStatus.PENDING) {\n task.status = FileTaskStatus.ERROR\n task.result = 'Interrupted'\n nb++\n this.cache.set(key, task).catch((e: Error) => this.logger.error(`${this.cleanupInterruptedTasks.name} - ${e}`))\n }\n }\n this.logger.log(`${this.cleanupInterruptedTasks.name} - ${nb} tasks cleaned : END`)\n } catch (e) {\n this.logger.error(`${this.cleanupInterruptedTasks.name} - ${e}`)\n }\n }\n\n @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)\n async cleanupUserTaskFiles(): Promise<void> {\n this.logger.log(`${this.cleanupUserTaskFiles.name} - START`)\n try {\n for (const user of await this.db\n .select({\n id: users.id,\n login: users.login,\n role: users.role\n })\n .from(users)) {\n const userTasksPath = UserModel.getTasksPath(user.login, user.role === USER_ROLE.GUEST, user.role === USER_ROLE.LINK)\n if (!(await isPathExists(userTasksPath))) {\n continue\n }\n if (await dirHasChildren(userTasksPath, false)) {\n const cacheKey = FilesTasksManager.getCacheKey(user.id)\n const keys = await this.cache.keys(cacheKey)\n const excludeFiles = (await this.cache.mget(keys))\n .filter((task: FileTask) => task && task.status === FileTaskStatus.PENDING && task.props.compressInDirectory === false)\n .map((task: FileTask) => task.name)\n for (const f of (await fs.readdir(userTasksPath)).filter((f: string) => excludeFiles.indexOf(f) === -1)) {\n try {\n removeFiles(path.join(userTasksPath, f)).catch((e: Error) => this.logger.error(`${this.cleanupUserTaskFiles.name} - ${e}`))\n } catch (e) {\n this.logger.error(`${this.cleanupUserTaskFiles.name} - unable to remove ${path.join(userTasksPath, f)} : ${e}`)\n }\n }\n }\n }\n } catch (e) {\n this.logger.error(`${this.cleanupUserTaskFiles.name} - ${e}`)\n }\n this.logger.log(`${this.cleanupUserTaskFiles.name} - END`)\n }\n\n @Cron(CronExpression.EVERY_8_HOURS)\n async clearRecentFiles(): Promise<void> {\n this.logger.log(`${this.clearRecentFiles.name} - START`)\n const keepNumber = 100\n let nbCleared = 0\n try {\n for (const fk of [filesRecents.ownerId, filesRecents.spaceId, filesRecents.shareId]) {\n const [r] = await this.db.execute(sql`\n DELETE\n FROM ${filesRecents}\n WHERE ${fk} IS NOT NULL\n AND id NOT IN (SELECT id\n FROM (SELECT id,\n ROW_NUMBER() OVER (PARTITION BY ${fk} ORDER BY ${filesRecents.mtime}) AS rn\n FROM ${filesRecents}\n WHERE ${fk} IS NOT NULL) AS ranked\n WHERE ranked.rn <= ${keepNumber})\n `)\n nbCleared += r.affectedRows\n }\n } catch (e) {\n this.logger.error(`${this.clearRecentFiles.name} - ${e}`)\n }\n this.logger.log(`${this.clearRecentFiles.name} - ${nbCleared} records cleared - END`)\n }\n\n @Cron(CronExpression.EVERY_4_HOURS)\n async indexContentFiles(): Promise<void> {\n // Conditional loading of file content indexing\n if (!configuration.applications.files.contentIndexing) return\n this.logger.log(`${this.indexContentFiles.name} - START`)\n await this.filesContentManager.parseAndIndexAllFiles()\n this.logger.log(`${this.indexContentFiles.name} - END`)\n }\n\n @Cron(CronExpression.EVERY_DAY_AT_4AM)\n async deleteOrphanFiles() {\n this.logger.log(`${this.deleteOrphanFiles.name} - START`)\n const selects: any[] = []\n for (const table of getTablesWithFileIdColumn()) {\n selects.push(this.db.selectDistinct({ id: table.fileId }).from(table).where(isNotNull(table.fileId)))\n }\n if (selects.length === 0) {\n this.logger.warn(`${this.deleteOrphanFiles.name} - no tables with fileId column`)\n return\n }\n const unionSub = (selects.length === 1 ? selects[0] : unionAll(...(selects as [any, any, ...any[]]))).as('u')\n // Debug\n // const [preview] = (await this.db.execute(sql`\n // SELECT f.id\n // FROM ${files} AS f\n // LEFT JOIN ${unionSub} ON ${unionSub.id} = f.id\n // WHERE ${unionSub.id} IS NULL\n // `)) as any[]\n // console.log(preview.length, preview)\n const deleteQuery = sql`\n DELETE f\n FROM ${files} AS f\n LEFT JOIN ${unionSub} ON ${unionSub.id} = f.id\n WHERE ${unionSub.id} IS NULL\n `\n try {\n await this.db.transaction(async (tx) => {\n const [r] = await tx.execute(deleteQuery)\n this.logger.log(`${this.deleteOrphanFiles.name} - files: ${r.affectedRows}`)\n })\n } catch (e) {\n this.logger.log(`${this.deleteOrphanFiles.name} - ${e}`)\n }\n this.logger.log(`${this.deleteOrphanFiles.name} - END`)\n }\n}\n"],"names":["FilesScheduler","onStartup","cleanupInterruptedTasks","clearRecentFiles","e","logger","error","afterStartup","indexContentFiles","log","name","nb","keys","cache","CACHE_TASK_PREFIX","key","task","get","status","FileTaskStatus","PENDING","ERROR","result","set","catch","cleanupUserTaskFiles","user","db","select","id","users","login","role","from","userTasksPath","UserModel","getTasksPath","USER_ROLE","GUEST","LINK","isPathExists","dirHasChildren","cacheKey","FilesTasksManager","getCacheKey","excludeFiles","mget","filter","props","compressInDirectory","map","f","fs","readdir","indexOf","removeFiles","path","join","keepNumber","nbCleared","fk","filesRecents","ownerId","spaceId","shareId","r","execute","sql","mtime","affectedRows","configuration","applications","files","contentIndexing","filesContentManager","parseAndIndexAllFiles","deleteOrphanFiles","selects","table","getTablesWithFileIdColumn","push","selectDistinct","fileId","where","isNotNull","length","warn","unionSub","unionAll","as","deleteQuery","transaction","tx","Logger","EVERY_DAY_AT_MIDNIGHT","EVERY_8_HOURS","EVERY_4_HOURS","EVERY_DAY_AT_4AM"],"mappings":"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"}
|
|
@@ -26,6 +26,9 @@ function _ts_metadata(k, v) {
|
|
|
26
26
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
27
27
|
}
|
|
28
28
|
let SpacesScheduler = class SpacesScheduler {
|
|
29
|
+
async onStartup() {
|
|
30
|
+
await this.updateQuotas();
|
|
31
|
+
}
|
|
29
32
|
async updateQuotas() {
|
|
30
33
|
this.logger.log('Update Personal Quotas - START');
|
|
31
34
|
try {
|
|
@@ -65,7 +68,12 @@ let SpacesScheduler = class SpacesScheduler {
|
|
|
65
68
|
}
|
|
66
69
|
};
|
|
67
70
|
_ts_decorate([
|
|
68
|
-
(0, _schedule.Timeout)(
|
|
71
|
+
(0, _schedule.Timeout)(60_000),
|
|
72
|
+
_ts_metadata("design:type", Function),
|
|
73
|
+
_ts_metadata("design:paramtypes", []),
|
|
74
|
+
_ts_metadata("design:returntype", Promise)
|
|
75
|
+
], SpacesScheduler.prototype, "onStartup", null);
|
|
76
|
+
_ts_decorate([
|
|
69
77
|
(0, _schedule.Cron)(_schedule.CronExpression.EVERY_HOUR),
|
|
70
78
|
_ts_metadata("design:type", Function),
|
|
71
79
|
_ts_metadata("design:paramtypes", []),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../backend/src/applications/spaces/services/spaces-scheduler.service.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Injectable, Logger } from '@nestjs/common'\nimport { Cron, CronExpression, Timeout } from '@nestjs/schedule'\nimport { SharesManager } from '../../shares/services/shares-manager.service'\nimport { SpacesManager } from './spaces-manager.service'\n\n@Injectable()\nexport class SpacesScheduler {\n private readonly logger = new Logger(SpacesScheduler.name)\n\n constructor(\n private readonly spacesManager: SpacesManager,\n private readonly sharesManager: SharesManager\n ) {}\n\n @Timeout(
|
|
1
|
+
{"version":3,"sources":["../../../../../backend/src/applications/spaces/services/spaces-scheduler.service.ts"],"sourcesContent":["/*\n * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>\n * This file is part of Sync-in | The open source file sync and share solution\n * See the LICENSE file for licensing details\n */\n\nimport { Injectable, Logger } from '@nestjs/common'\nimport { Cron, CronExpression, Timeout } from '@nestjs/schedule'\nimport { SharesManager } from '../../shares/services/shares-manager.service'\nimport { SpacesManager } from './spaces-manager.service'\n\n@Injectable()\nexport class SpacesScheduler {\n private readonly logger = new Logger(SpacesScheduler.name)\n\n constructor(\n private readonly spacesManager: SpacesManager,\n private readonly sharesManager: SharesManager\n ) {}\n\n @Timeout(60_000)\n async onStartup() {\n await this.updateQuotas()\n }\n\n @Cron(CronExpression.EVERY_HOUR)\n async updateQuotas() {\n this.logger.log('Update Personal Quotas - START')\n try {\n await this.spacesManager.updatePersonalSpacesQuota()\n } catch (e) {\n this.logger.error(`Update Personal Quotas} - ${e}`)\n }\n this.logger.log('Update Personal Quotas - END')\n this.logger.log('Update Space Quotas - START')\n try {\n await this.spacesManager.updateSpacesQuota()\n } catch (e) {\n this.logger.error(`Update Space Quotas - ${e}`)\n }\n this.logger.log('Update Space Quotas - END')\n this.logger.log('Update Share External Path Quotas - START')\n try {\n await this.sharesManager.updateSharesExternalPathQuota()\n } catch (e) {\n this.logger.error(`Update Share External Path Quotas - ${e}`)\n }\n this.logger.log('Update Share External Path Quotas - END')\n }\n\n @Cron(CronExpression.EVERY_DAY_AT_2AM)\n async deleteExpiredSpaces() {\n /* Removes spaces that have been disabled for more than 30 days */\n this.logger.log(`${this.deleteExpiredSpaces.name} - START`)\n try {\n await this.spacesManager.deleteExpiredSpaces()\n } catch (e) {\n this.logger.error(`${this.deleteExpiredSpaces.name} - ${e}`)\n }\n this.logger.log(`${this.deleteExpiredSpaces.name} - DONE`)\n }\n}\n"],"names":["SpacesScheduler","onStartup","updateQuotas","logger","log","spacesManager","updatePersonalSpacesQuota","e","error","updateSpacesQuota","sharesManager","updateSharesExternalPathQuota","deleteExpiredSpaces","name","Logger","EVERY_HOUR","EVERY_DAY_AT_2AM"],"mappings":"AAAA;;;;CAIC;;;;+BAQYA;;;eAAAA;;;wBANsB;0BACW;sCAChB;sCACA;;;;;;;;;;AAGvB,IAAA,AAAMA,kBAAN,MAAMA;IAQX,MACMC,YAAY;QAChB,MAAM,IAAI,CAACC,YAAY;IACzB;IAEA,MACMA,eAAe;QACnB,IAAI,CAACC,MAAM,CAACC,GAAG,CAAC;QAChB,IAAI;YACF,MAAM,IAAI,CAACC,aAAa,CAACC,yBAAyB;QACpD,EAAE,OAAOC,GAAG;YACV,IAAI,CAACJ,MAAM,CAACK,KAAK,CAAC,CAAC,0BAA0B,EAAED,GAAG;QACpD;QACA,IAAI,CAACJ,MAAM,CAACC,GAAG,CAAC;QAChB,IAAI,CAACD,MAAM,CAACC,GAAG,CAAC;QAChB,IAAI;YACF,MAAM,IAAI,CAACC,aAAa,CAACI,iBAAiB;QAC5C,EAAE,OAAOF,GAAG;YACV,IAAI,CAACJ,MAAM,CAACK,KAAK,CAAC,CAAC,sBAAsB,EAAED,GAAG;QAChD;QACA,IAAI,CAACJ,MAAM,CAACC,GAAG,CAAC;QAChB,IAAI,CAACD,MAAM,CAACC,GAAG,CAAC;QAChB,IAAI;YACF,MAAM,IAAI,CAACM,aAAa,CAACC,6BAA6B;QACxD,EAAE,OAAOJ,GAAG;YACV,IAAI,CAACJ,MAAM,CAACK,KAAK,CAAC,CAAC,oCAAoC,EAAED,GAAG;QAC9D;QACA,IAAI,CAACJ,MAAM,CAACC,GAAG,CAAC;IAClB;IAEA,MACMQ,sBAAsB;QAC1B,gEAAgE,GAChE,IAAI,CAACT,MAAM,CAACC,GAAG,CAAC,GAAG,IAAI,CAACQ,mBAAmB,CAACC,IAAI,CAAC,QAAQ,CAAC;QAC1D,IAAI;YACF,MAAM,IAAI,CAACR,aAAa,CAACO,mBAAmB;QAC9C,EAAE,OAAOL,GAAG;YACV,IAAI,CAACJ,MAAM,CAACK,KAAK,CAAC,GAAG,IAAI,CAACI,mBAAmB,CAACC,IAAI,CAAC,GAAG,EAAEN,GAAG;QAC7D;QACA,IAAI,CAACJ,MAAM,CAACC,GAAG,CAAC,GAAG,IAAI,CAACQ,mBAAmB,CAACC,IAAI,CAAC,OAAO,CAAC;IAC3D;IA7CA,YACE,AAAiBR,aAA4B,EAC7C,AAAiBK,aAA4B,CAC7C;aAFiBL,gBAAAA;aACAK,gBAAAA;aAJFP,SAAS,IAAIW,cAAM,CAACd,gBAAgBa,IAAI;IAKtD;AA2CL;;;;;;;;iDApCuBE;;;;;;iDAyBAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as ie,b as ne,c as ae}from"./chunk-2CAAJBRO.js";import{Ab as te,qb as I,s as q,xb as D,yb as $,zb as ee}from"./chunk-
|
|
1
|
+
import{a as ie,b as ne,c as ae}from"./chunk-2CAAJBRO.js";import{Ab as te,qb as I,s as q,xb as D,yb as $,zb as ee}from"./chunk-FCR5AEHR.js";import{Ae as T,Cd as B,Ed as j,Gb as _,Jd as k,Jf as Z,Kd as G,Ld as x,Oa as t,Rb as P,Sb as s,Tb as r,Ub as M,Vb as R,Wd as J,Xb as g,Yb as y,Ye as X,Zb as h,Zf as N,ab as u,cc as d,fd as H,ja as O,jd as w,kc as L,lb as C,mb as f,mc as Y,od as F,og as v,qd as z,qe as K,rb as a,sb as i,se as Q,tb as l,ub as m,ud as b,vd as W,wd as U,xd as V}from"./chunk-BJARRIS6.js";function se(o,p){if(o&1&&m(0,"fa-icon",1),o&2){let n=_(2);a("icon",n.icons.faLongArrowAltDown)("fixedWidth",n.syncPath.settings.mode!==n.SYNC_PATH_MODE.BOTH)}}function ce(o,p){if(o&1&&m(0,"fa-icon",3),o&2){let n=_(2);s(n.SYNC_TRANSFER_SIDE_CLASS[n.SYNC_TRANSFER_SIDE.LOCAL]),a("icon",n.SYNC_TRANSFER_SIDE_ICON[n.SYNC_TRANSFER_SIDE.LOCAL])}}function re(o,p){if(o&1&&C(0,se,1,2,"fa-icon",1)(1,ce,1,3,"fa-icon",2),o&2){let n=_();f(n.small?0:1)}}function de(o,p){if(o&1&&m(0,"fa-icon",1),o&2){let n=_(2);a("icon",n.icons.faLongArrowAltUp)("fixedWidth",n.syncPath.settings.mode!==n.SYNC_PATH_MODE.BOTH)}}function pe(o,p){if(o&1&&m(0,"fa-icon",3),o&2){let n=_(2);s(n.SYNC_TRANSFER_SIDE_CLASS[n.SYNC_TRANSFER_SIDE.REMOTE]),P("ms-1",n.syncPath.settings.mode===n.SYNC_PATH_MODE.BOTH),a("icon",n.SYNC_TRANSFER_SIDE_ICON[n.SYNC_TRANSFER_SIDE.REMOTE])}}function me(o,p){if(o&1&&C(0,de,1,2,"fa-icon",1)(1,pe,1,5,"fa-icon",4),o&2){let n=_();f(n.small?0:1)}}var oe=(()=>{let p=class p{constructor(){this.small=!1,this.SYNC_PATH_MODE=D,this.SYNC_TRANSFER_SIDE_ICON=ae,this.icons={faLongArrowAltDown:N,faLongArrowAltUp:T},this.SYNC_TRANSFER_SIDE=ie,this.SYNC_TRANSFER_SIDE_CLASS=ne}};p.\u0275fac=function(S){return new(S||p)},p.\u0275cmp=u({type:p,selectors:[["app-sync-path-direction-icon"]],inputs:{syncPath:"syncPath",small:"small"},decls:3,vars:2,consts:[[1,"d-flex","justify-content-center"],[3,"icon","fixedWidth"],[3,"icon","class"],[3,"icon"],[3,"icon","ms-1","class"]],template:function(S,e){S&1&&(i(0,"span",0),C(1,re,2,1),C(2,me,2,1),l()),S&2&&(t(),f(e.syncPath.settings.mode===e.SYNC_PATH_MODE.DOWNLOAD||e.syncPath.settings.mode===e.SYNC_PATH_MODE.BOTH?1:-1),t(),f(e.syncPath.settings.mode===e.SYNC_PATH_MODE.UPLOAD||e.syncPath.settings.mode===e.SYNC_PATH_MODE.BOTH?2:-1))},dependencies:[v],encapsulation:2});let o=p;return o})();function _e(o,p){if(o&1&&(i(0,"div")(1,"div"),m(2,"fa-icon",0),i(3,"span",1),r(4,"Client"),l()(),i(5,"div",10)(6,"span",11),r(7),l()()(),i(8,"div")(9,"div"),m(10,"fa-icon",0),i(11,"span",1),r(12,"Server"),l()(),i(13,"div",10)(14,"span",11)(15,"div",12),m(16,"fa-icon",13),r(17),l()()()()),o&2){let n=_();s(d("d-flex justify-content-",n.direction," align-items-center mb-2")),t(),s(d("col-",n.colSize[n.size][0])),t(),a("icon",n.icons.CLIENT),t(5),M(n.syncPath.settings.localPath),t(),s(d("d-flex justify-content-",n.direction," align-items-center mb-2")),t(),s(d("col-",n.colSize[n.size][0])),t(),a("icon",n.icons.SERVER),t(6),a("icon",n.syncPath.icon),t(),R(" ",n.syncPath.showedPath," ")}}function Se(o,p){if(o&1&&(i(0,"span",4),m(1,"fa-icon",14),L(2,"translate"),l()),o&2){let n=_();t(),a("icon",n.icons.faExclamationCircle)("tooltip",Y(2,2,"You must have permission to modify the server folder to choose a different sync mode",n.locale.language))}}var Le=(()=>{let p=class p{constructor(){this.direction="center",this.showPaths=!1,this.size="small",this.locale=O(j),this.icons={CLIENT:I.CLIENT,SERVER:I.SERVER,faExclamationCircle:X,faLongArrowAltDown:N,faLongArrowAltUp:T,faGauge:J,faClock:Q,faRotate:x,faEdit:K,faBug:Z},this.colSize={small:[2,5],large:[3,6]},this.SYNC_PATH_CONFLICT_MODE=ee,this.SYNC_PATH_MODE=D,this.SYNC_PATH_DIFF_MODE=$,this.SYNC_PATH_SCHEDULER_UNIT=te}};p.\u0275fac=function(S){return new(S||p)},p.\u0275cmp=u({type:p,selectors:[["app-sync-path-settings"]],inputs:{syncPath:"syncPath",direction:"direction",showPaths:"showPaths",size:"size"},decls:74,vars:98,consts:[[3,"icon"],["l10nTranslate","",1,"ms-2"],["type","text",3,"ngModelChange","ngModel"],[3,"syncPath","small"],[1,"ms-2","fs-lg","cursor-pointer"],[1,"form-select","form-select-sm",3,"ngModelChange","ngModel"],["l10nTranslate","",3,"disabled","ngValue"],["l10nTranslate","",3,"ngValue"],[1,"form-select","form-select-sm",3,"ngModelChange","ngModel","disabled"],["min","1","type","number",1,"form-control","form-select-sm","pe-1","me-2",2,"width","70px",3,"ngModelChange","ngModel","disabled"],[1,"col-8"],[1,"form-control-sm","form-control-plaintext"],[1,"d-flex","align-items-center"],[1,"me-1",3,"icon"],[1,"text-warning",3,"icon","tooltip"]],template:function(S,e){S&1&&(i(0,"div"),C(1,_e,18,17),i(2,"div")(3,"div"),m(4,"fa-icon",0),i(5,"span",1),r(6,"Name"),l()(),i(7,"div")(8,"input",2),h("ngModelChange",function(c){return y(e.syncPath.settings.name,c)||(e.syncPath.settings.name=c),c}),l()()(),i(9,"div")(10,"div"),m(11,"app-sync-path-direction-icon",3),i(12,"span",1),r(13,"Direction"),l(),C(14,Se,3,5,"span",4),l(),i(15,"div")(16,"select",5),h("ngModelChange",function(c){return y(e.syncPath.settings.mode,c)||(e.syncPath.settings.mode=c),c}),i(17,"option",6),r(18,"upload only"),l(),i(19,"option",7),r(20,"download only"),l(),i(21,"option",6),r(22,"both"),l()()()(),i(23,"div")(24,"div"),m(25,"fa-icon",0),i(26,"span",1),r(27,"Conflict"),l()(),i(28,"div")(29,"select",8),h("ngModelChange",function(c){return y(e.syncPath.settings.conflictMode,c)||(e.syncPath.settings.conflictMode=c),c}),i(30,"option",7),r(31,"the client\u2019s files take precedence"),l(),i(32,"option",7),r(33,"the server\u2019s files take precedence"),l(),i(34,"option",7),r(35,"the most recent files will be kept"),l()()()(),i(36,"div")(37,"div"),m(38,"fa-icon",0),i(39,"span",1),r(40,"Mode"),l()(),i(41,"div")(42,"select",5),h("ngModelChange",function(c){return y(e.syncPath.settings.diffMode,c)||(e.syncPath.settings.diffMode=c),c}),i(43,"option",7),r(44),l(),i(45,"option",7),r(46),l()()()(),i(47,"div")(48,"div"),m(49,"fa-icon",0),i(50,"span",1),r(51,"Scheduler"),l()(),i(52,"div")(53,"input",9),h("ngModelChange",function(c){return y(e.syncPath.settings.scheduler.value,c)||(e.syncPath.settings.scheduler.value=c),c}),l(),i(54,"select",5),h("ngModelChange",function(c){return y(e.syncPath.settings.scheduler.unit,c)||(e.syncPath.settings.scheduler.unit=c),c}),i(55,"option",7),r(56,"disabled"),l(),i(57,"option",7),r(58,"scheduler_unit_minute"),l(),i(59,"option",7),r(60,"scheduler_unit_hour"),l(),i(61,"option",7),r(62,"scheduler_unit_day"),l()()()(),i(63,"div")(64,"div"),m(65,"fa-icon",0),i(66,"span",1),r(67,"Status"),l()(),i(68,"div")(69,"select",5),h("ngModelChange",function(c){return y(e.syncPath.settings.enabled,c)||(e.syncPath.settings.enabled=c),c}),i(70,"option",7),r(71,"enabled"),l(),i(72,"option",7),r(73,"disabled"),l()()()()()),S&2&&(s(d("d-flex flex-column justify-content-",e.direction)),t(),f(e.showPaths?1:-1),t(),s(d("d-flex justify-content-",e.direction," align-items-center mb-2")),t(),s(d("col-",e.colSize[e.size][0])),t(),a("icon",e.icons.faEdit),t(3),s(d("col-",e.colSize[e.size][1])),t(),s(d("form-control form-select-sm ",e.syncPath.settings.name?"":"is-invalid")),g("ngModel",e.syncPath.settings.name),t(),s(d("d-flex justify-content-",e.direction," align-items-center mb-2")),t(),s(d("d-flex align-items-center col-",e.colSize[e.size][0])),t(),a("syncPath",e.syncPath)("small",!0),t(3),f(e.syncPath.isWriteable?-1:14),t(),s(d("col-",e.colSize[e.size][1])),t(),g("ngModel",e.syncPath.settings.mode),t(),a("disabled",!e.syncPath.isWriteable)("ngValue",e.SYNC_PATH_MODE.UPLOAD),t(2),a("ngValue",e.SYNC_PATH_MODE.DOWNLOAD),t(2),a("disabled",!e.syncPath.isWriteable)("ngValue",e.SYNC_PATH_MODE.BOTH),t(2),s(d("d-flex justify-content-",e.direction," align-items-center mb-2")),t(),s(d("col-",e.colSize[e.size][0])),t(),a("icon",e.icons.faBug),t(3),s(d("col-",e.colSize[e.size][1])),t(),g("ngModel",e.syncPath.settings.conflictMode),a("disabled",e.syncPath.settings.mode!=="both"),t(),a("ngValue",e.SYNC_PATH_CONFLICT_MODE.LOCAL),t(2),a("ngValue",e.SYNC_PATH_CONFLICT_MODE.REMOTE),t(2),a("ngValue",e.SYNC_PATH_CONFLICT_MODE.RECENT),t(2),s(d("d-flex justify-content-",e.direction," align-items-center mb-2")),t(),s(d("col-",e.colSize[e.size][0])),t(),a("icon",e.icons.faGauge),t(3),s(d("col-",e.colSize[e.size][1])),t(),g("ngModel",e.syncPath.settings.diffMode),t(),a("ngValue",e.SYNC_PATH_DIFF_MODE.FAST),t(),M(e.SYNC_PATH_DIFF_MODE.FAST),t(),a("ngValue",e.SYNC_PATH_DIFF_MODE.SECURE),t(),M(e.SYNC_PATH_DIFF_MODE.SECURE),t(),s(d("d-flex justify-content-",e.direction," align-items-center mb-2")),t(),s(d("col-",e.colSize[e.size][0])),t(),a("icon",e.icons.faClock),t(3),s(d("d-flex flex-row col-",e.colSize[e.size][1])),t(),g("ngModel",e.syncPath.settings.scheduler.value),a("disabled",e.syncPath.settings.scheduler.unit===e.SYNC_PATH_SCHEDULER_UNIT.DISABLED),t(),g("ngModel",e.syncPath.settings.scheduler.unit),t(),a("ngValue",e.SYNC_PATH_SCHEDULER_UNIT.DISABLED),t(2),a("ngValue",e.SYNC_PATH_SCHEDULER_UNIT.MINUTE),t(2),a("ngValue",e.SYNC_PATH_SCHEDULER_UNIT.HOUR),t(2),a("ngValue",e.SYNC_PATH_SCHEDULER_UNIT.DAY),t(2),s(d("d-flex justify-content-",e.direction," align-items-center")),t(),s(d("col-",e.colSize[e.size][0])),t(),a("icon",e.icons.faRotate),t(3),s(d("col-",e.colSize[e.size][1])),t(),P("text-danger",!e.syncPath.settings.enabled),g("ngModel",e.syncPath.settings.enabled),t(),a("ngValue",!0),t(2),a("ngValue",!1))},dependencies:[G,v,B,W,U,H,z,b,w,V,F,q,oe,k],encapsulation:2});let o=p;return o})();export{oe as a,Le as b};
|