Support tool, zips folder and puts it to users home directory (#870)

* Support tool, zips folder and puts it in users home directory
This commit is contained in:
Anton Shepilov
2025-04-07 14:25:04 +02:00
committed by GitHub
parent 755c4f05e2
commit 22e30d20fb
9 changed files with 470 additions and 6 deletions
+261
View File
@@ -0,0 +1,261 @@
{
"general": {
"help_url": false,
"pricing_plan_url": "",
"app_download_url": "https://twake.app/download",
"mobile": {
"mobile_redirect": "mobile.twake.app",
"mobile_appstore": "https://apps.apple.com/fr/app/tdrive/id1588764852?l=en",
"mobile_googleplay": "https://play.google.com/store/apps/details?id=com.tdrive.tdrive&gl=FR"
},
"accounts": {
"type": "internal",
"internal": {
"disable_account_creation": false
},
"remote": {
"authority": "http://auth.example.com/",
"client_id": "tdriveweb",
"client_secret": "",
"issuer": "",
"audience": "",
"redirect_uris": [""],
"account_management_url": "http://web.tdrive-console.local/profile?company-code={company_id}",
"collaborators_management_url": "http://web.tdrive-console.local/compaies/{company_id}/users?company-code={company_id}",
"company_management_url": "http://web.tdrive-console.local/companies?company-code={company_id}"
}
}
},
"console": {
"type": "internal"
},
"sentry": {
"dsn": ""
},
"logger": {
"level": "debug"
},
"admin": {
"endpointSecret": ""
},
"diagnostics": {
"skipKeys": [],
"probeSecret": "",
"statsLogPeriodMs": 120000,
"statsFullStatsLogPeriodMs": 600000
},
"tracker": {
"type": "segment",
"segment": {
"key": ""
}
},
"webserver": {
"host": "localhost",
"port": 4000,
"logger": {
"level": "info"
},
"cors": {
"origin": "*"
},
"static": {
"root": "./public"
}
},
"auth": {
"jwt": {
"secret": "supersecret",
"expiration": 3600,
"refresh_expiration": 2592000
}
},
"database": {
"secret": "",
"type": "mongodb",
"encryption": "aes-256-cbc",
"mongodb": {
"uri": "mongodb://localhost:27017",
"database": "tdrive"
}
},
"message-queue": {
"type": "local",
"amqp": {
"urls": ["amqp://guest:guest@rabbitmq:5672"]
}
},
"search": {
"type": "mongodb",
"elasticsearch": {
"endpoint": "http://elasticsearch:9200"
}
},
"push": {
"type": false,
"fcm": {
"endpoint": "https://fcm.googleapis.com/fcm/send",
"key": ""
}
},
"storage": {
"secret": "",
"iv": "1234abcd00000000",
"strategy": "local",
"type": "local",
"S3": {
"endPoint": "play.min.io",
"port": 9000,
"useSSL": false,
"accessKey": "ABCD",
"secretKey": "x1yz",
"disableRemove": false
},
"local": {
"path": "/Users/shepilov/dev/linagora/TDrive/tdrive/docker-data/"
}
},
"email-pusher": {
"endpoint": "https://api.smtp2go.com/v3/email/send",
"api_key": "secret",
"sender": "noreply@twake.app",
"debug": true
},
"drive": {
"featureSharedDrive": true,
"featureDisplayEmail": true,
"featureUserQuota": false,
"featureManageAccess": true,
"defaultCompany": "00000000-0000-4000-0000-000000000000",
"defaultUserQuota": 200000000,
"featureAntivirus": false
},
"av": {
"host": "av",
"port": 3310,
"debugMode": false,
"timeout": 2000,
"maxFileSize": 26214400
},
"applications": {
"grid": [
{
"name": "Chat",
"logo": "/public/img/grid/twake.svg",
"url": "https://web.twake.app/"
},
{
"name": "Mail",
"logo": "/public/img/grid/mail.svg",
"url": "https://tmail.linagora.com/"
},
{
"name": "Drive",
"logo": "/public/img/grid/drive.svg",
"url": "https://tdrive.qa.lin-saas.com/"
},
{
"name": "Calendar",
"logo": "/public/img/grid/calendar.svg",
"url": "https://openpaas.linagora.com/calendar/"
},
{
"name": "Contacts",
"logo": "/public/img/grid/contacts.svg",
"url": "https://openpaas.linagora.com/contacts"
},
{
"name": "Visio",
"logo": "/public/img/grid/visio.svg",
"url": "https://jitsi.linagora.com/"
}
],
"plugins": [
{
"api": {
"private_key": ""
},
"display": {
"tdrive": {
"files": {
"editor": {
"edition_url": "http://localhost:5000/plugins/onlyoffice/",
"empty_files": [
{
"filename": "Untitled.docx",
"name": "ONLYOFFICE Word Document",
"url": "/plugins/onlyoffice/assets/empty.docx"
},
{
"filename": "Untitled.xlsx",
"name": "ONLYOFFICE Excel Document",
"url": "/plugins/onlyoffice/assets/empty.xlsx"
},
{
"filename": "Untitled.pptx",
"name": "ONLYOFFICE PowerPoint Document",
"url": "/plugins/onlyoffice/assets/empty.pptx"
}
],
"extensions": [
"xlsx",
"pptx",
"docx",
"xls",
"ppt",
"doc",
"odt",
"ods",
"odp",
"txt",
"html",
"csv"
],
"preview_url": "http://localhost:5000/plugins/onlyoffice/?preview=1"
}
},
"version": 1
}
},
"external_prefix": "/plugins/onlyoffice/",
"id": "tdrive_onlyoffice",
"identity": {
"categories": [],
"code": "only_office",
"compatibility": ["tdrive"],
"description": null,
"icon": "/plugins/onlyoffice/assets/logo.png",
"name": "Only Office",
"website": "http://twake.app/"
},
"internal_domain": "http://localhost:5000/"
}
]
},
"services": [
"admin",
"auth",
"diagnostics",
"push",
"storage",
"webserver",
"database",
"cron",
"search",
"message-queue",
"tracker",
"general",
"user",
"files",
"workspaces",
"console",
"counter",
"statistics",
"cron",
"email-pusher",
"documents",
"applications",
"applications-api",
"tags"
]
}
+36
View File
@@ -36,6 +36,7 @@
"bcrypt": "^5.0.1", "bcrypt": "^5.0.1",
"clamscan": "^2.4.0", "clamscan": "^2.4.0",
"class-transformer": "^0.3.1", "class-transformer": "^0.3.1",
"cli-interact": "^0.1.9",
"cli-table": "^0.3.6", "cli-table": "^0.3.6",
"config": "^3.3.2", "config": "^3.3.2",
"deep-object-diff": "^1.1.0", "deep-object-diff": "^1.1.0",
@@ -96,6 +97,7 @@
"@types/analytics-node": "^3.1.5", "@types/analytics-node": "^3.1.5",
"@types/archiver": "^6.0.3", "@types/archiver": "^6.0.3",
"@types/bcrypt": "^5.0.0", "@types/bcrypt": "^5.0.0",
"@types/cli-interact": "^0.1.4",
"@types/cli-table": "^0.3.0", "@types/cli-table": "^0.3.0",
"@types/config": "0.0.36", "@types/config": "0.0.36",
"@types/eslint": "^7.2.3", "@types/eslint": "^7.2.3",
@@ -2637,6 +2639,15 @@
"@types/responselike": "^1.0.0" "@types/responselike": "^1.0.0"
} }
}, },
"node_modules/@types/cli-interact": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/@types/cli-interact/-/cli-interact-0.1.4.tgz",
"integrity": "sha512-ApnXGFz+j6Yb6v6UvD3c9eEMQG+4YARy/+ZkwQIMBW+HTuwtAw6QknlE2rdkVHuPdt+dn4WsArQjS1K/LT6L/w==",
"dev": true,
"dependencies": {
"@types/readline-sync": "*"
}
},
"node_modules/@types/cli-table": { "node_modules/@types/cli-table": {
"version": "0.3.4", "version": "0.3.4",
"resolved": "https://registry.npmjs.org/@types/cli-table/-/cli-table-0.3.4.tgz", "resolved": "https://registry.npmjs.org/@types/cli-table/-/cli-table-0.3.4.tgz",
@@ -2943,6 +2954,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/readline-sync": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/@types/readline-sync/-/readline-sync-1.4.8.tgz",
"integrity": "sha512-BL7xOf0yKLA6baAX6MMOnYkoflUyj/c7y3pqMRfU0va7XlwHAOTOIo4x55P/qLfMsuaYdJJKubToLqRVmRtRZA==",
"dev": true
},
"node_modules/@types/responselike": { "node_modules/@types/responselike": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz",
@@ -4560,6 +4577,17 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/cli-interact": {
"version": "0.1.9",
"resolved": "https://registry.npmjs.org/cli-interact/-/cli-interact-0.1.9.tgz",
"integrity": "sha512-QHrR2djQOfT1WfHSj9DidBGp+ImgV3t9dTP4cPWTnt9fnOcZqBzCoqd2RysWSIgUXuer5WpmKuT1jqjvwoxdCQ==",
"dependencies": {
"readline-sync": "~0.4.9"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/cli-spinners": { "node_modules/cli-spinners": {
"version": "2.9.2", "version": "2.9.2",
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
@@ -10957,6 +10985,14 @@
"node": ">=8.10.0" "node": ">=8.10.0"
} }
}, },
"node_modules/readline-sync": {
"version": "0.4.10",
"resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-0.4.10.tgz",
"integrity": "sha512-q/RyXCZ+49a7octyBMENisHi11JVcnNORLDKEdKEzUnBBiszElOtdCY+wJKPCnwHuh/8+yfDoptGL7D79yUOLw==",
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/real-require": { "node_modules/real-require": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
+2
View File
@@ -63,6 +63,7 @@
"@types/analytics-node": "^3.1.5", "@types/analytics-node": "^3.1.5",
"@types/archiver": "^6.0.3", "@types/archiver": "^6.0.3",
"@types/bcrypt": "^5.0.0", "@types/bcrypt": "^5.0.0",
"@types/cli-interact": "^0.1.4",
"@types/cli-table": "^0.3.0", "@types/cli-table": "^0.3.0",
"@types/config": "0.0.36", "@types/config": "0.0.36",
"@types/eslint": "^7.2.3", "@types/eslint": "^7.2.3",
@@ -133,6 +134,7 @@
"bcrypt": "^5.0.1", "bcrypt": "^5.0.1",
"clamscan": "^2.4.0", "clamscan": "^2.4.0",
"class-transformer": "^0.3.1", "class-transformer": "^0.3.1",
"cli-interact": "^0.1.9",
"cli-table": "^0.3.6", "cli-table": "^0.3.6",
"config": "^3.3.2", "config": "^3.3.2",
"deep-object-diff": "^1.1.0", "deep-object-diff": "^1.1.0",
@@ -0,0 +1,141 @@
import yargs from "yargs";
import runWithPlatform from "../../lib/run-with-platform";
import gr from "../../../services/global-resolver";
import { messageQueueLogger, platformLogger } from "../../../core/platform/framework";
import User, { TYPE as UserTYPE } from "../../../services/user/entities/user";
import config from "config";
import globalResolver from "../../../services/global-resolver";
import fs from "fs";
import { CompanyExecutionContext, DriveItemDetails } from "../../../services/documents/types";
import { randomUUID } from "crypto";
import { getYesNo } from "cli-interact";
type ZipCommandArgs = {
verbose: boolean;
user: string;
folder: string;
};
/**
*
*/
export default {
command: "zip",
describe: "Zips specified folder and puts it to users root directory",
builder: {
user: {
alias: "u",
demandOption: true,
type: "string",
describe: "id or login or email of the user.",
},
folder: {
alias: "f",
demandOption: true,
type: "string",
describe: "folder identifier to zip",
},
},
handler: async (argv: ZipCommandArgs) => {
if (!argv.verbose) {
platformLogger.level = "warn";
messageQueueLogger.level = "warn";
}
await runWithPlatform("zip", async ({ spinner: _spinner, platform: _platform }) => {
//create drive execution context for a specified user
const context = await getContext(argv);
//get folder to zip, and make all necessary checks
const toZip = await gr.services.documents.documents.get(argv.folder, {}, context);
//zip folder and pipe it to new file in storage
const tmpFilePath = `/tmp/${argv.folder}-${randomUUID()}.zip`;
await zipFolder(toZip, tmpFilePath, context);
_spinner.info(`Folder successfully zipped to "${tmpFilePath}"`);
//create a new document with this file in users root directory
if (getYesNo("Do you want to upload file?")) {
await saveFile(tmpFilePath, `${toZip.item.name}.zip`, context);
_spinner.info(`"${toZip.item.name}.zip" saved to users home directory`);
fs.rmSync(tmpFilePath);
_spinner.info("Tmp files cleaned");
}
return 0;
});
},
} as yargs.CommandModule<object, ZipCommandArgs>;
async function saveFile(path: string, filename: string, context: CompanyExecutionContext) {
const file = await gr.services.files.save(
null,
fs.createReadStream(path),
{
totalChunks: 1,
totalSize: fs.statSync(path).size,
chunkNumber: 1,
filename: filename,
type: "application/zip",
waitForThumbnail: false,
ignoreThumbnails: true,
},
context,
);
const driveItem = await gr.services.documents.documents.create(
file,
{
name: filename,
parent_id: `user_${context.user.id}`,
is_directory: false,
},
{
file_metadata: {
source: "internal",
external_id: file.id,
name: filename,
mime: "application/zip",
size: file.upload_data.size,
},
},
context,
);
console.log(driveItem);
}
async function getContext(argv: ZipCommandArgs): Promise<CompanyExecutionContext> {
const user = await findUser(argv.user);
if (!user) {
throw new Error(`Error, unknown user ${JSON.stringify(argv.user)}`);
}
//create drive execution context for a specified user
const company = config.get<string>("drive.defaultCompany");
if (!company) {
throw new Error(`Error, unknown company in configuration ${company}`);
}
return { user, company: { id: company } };
}
async function findUser(identifier: string): Promise<User | null> {
const userRepo = await gr.database.getRepository<User>(UserTYPE, User);
let user = await userRepo.findOne({ id: identifier });
if (!user) user = await userRepo.findOne({ email_canonical: identifier });
if (!user) user = await userRepo.findOne({ username_canonical: identifier });
return user;
}
async function zipFolder(
toZip: DriveItemDetails,
outputPath: string,
context: CompanyExecutionContext,
) {
return new Promise<void>(async (resolve, reject) => {
return await globalResolver.services.documents.documents.download(
toZip.item.id,
null,
archive => {
archive.pipe(fs.createWriteStream(outputPath));
archive.on("finish", resolve);
archive.on("error", reject);
},
context,
);
});
}
@@ -1405,7 +1405,7 @@ export class DocumentsService {
if (file) { if (file) {
const fileEntity = await globalResolver.services.files.save( const fileEntity = await globalResolver.services.files.save(
null, null,
file, file.file,
{ {
...options, ...options,
filename: options.filename ?? driveFile.name, filename: options.filename ?? driveFile.name,
@@ -67,7 +67,7 @@ export class DocumentsController {
ignoreThumbnails: true, ignoreThumbnails: true,
}; };
createdFile = await globalResolver.services.files.save(null, file, options, context); createdFile = await globalResolver.services.files.save(null, file?.file, options, context);
} }
const { item, version } = request.body; const { item, version } = request.body;
@@ -73,7 +73,7 @@ export class FileServiceImpl {
async save( async save(
id: string, id: string,
file: MultipartFile, file: Readable,
options: UploadOptions, options: UploadOptions,
context: CompanyExecutionContext, context: CompanyExecutionContext,
): Promise<File> { ): Promise<File> {
@@ -136,10 +136,10 @@ export class FileServiceImpl {
} }
let totalUploadedSize = 0; let totalUploadedSize = 0;
file.file.on("data", function (chunk) { file.on("data", function (chunk) {
totalUploadedSize += chunk.length; totalUploadedSize += chunk.length;
}); });
await gr.platformServices.storage.write(getFilePath(entity), file.file, { await gr.platformServices.storage.write(getFilePath(entity), file, {
chunkNumber: options.chunkNumber, chunkNumber: options.chunkNumber,
encryptionAlgo: this.algorithm, encryptionAlgo: this.algorithm,
encryptionKey: entity.encryption_key, encryptionKey: entity.encryption_key,
@@ -34,7 +34,7 @@ export class FileController {
}; };
const id = request.params.id; const id = request.params.id;
const result = await gr.services.files.save(id, file, options, context); const result = await gr.services.files.save(id, file?.file, options, context);
return { return {
resource: result.getPublicObject(), resource: result.getPublicObject(),
+24
View File
@@ -1170,6 +1170,13 @@
"@types/node" "*" "@types/node" "*"
"@types/responselike" "^1.0.0" "@types/responselike" "^1.0.0"
"@types/cli-interact@^0.1.4":
version "0.1.4"
resolved "https://registry.npmjs.org/@types/cli-interact/-/cli-interact-0.1.4.tgz"
integrity sha512-ApnXGFz+j6Yb6v6UvD3c9eEMQG+4YARy/+ZkwQIMBW+HTuwtAw6QknlE2rdkVHuPdt+dn4WsArQjS1K/LT6L/w==
dependencies:
"@types/readline-sync" "*"
"@types/cli-table@^0.3.0": "@types/cli-table@^0.3.0":
version "0.3.4" version "0.3.4"
resolved "https://registry.npmjs.org/@types/cli-table/-/cli-table-0.3.4.tgz" resolved "https://registry.npmjs.org/@types/cli-table/-/cli-table-0.3.4.tgz"
@@ -1440,6 +1447,11 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/readline-sync@*":
version "1.4.8"
resolved "https://registry.npmjs.org/@types/readline-sync/-/readline-sync-1.4.8.tgz"
integrity sha512-BL7xOf0yKLA6baAX6MMOnYkoflUyj/c7y3pqMRfU0va7XlwHAOTOIo4x55P/qLfMsuaYdJJKubToLqRVmRtRZA==
"@types/responselike@^1.0.0": "@types/responselike@^1.0.0":
version "1.0.3" version "1.0.3"
resolved "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz" resolved "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz"
@@ -2380,6 +2392,13 @@ cli-cursor@^3.1.0:
dependencies: dependencies:
restore-cursor "^3.1.0" restore-cursor "^3.1.0"
cli-interact@^0.1.9:
version "0.1.9"
resolved "https://registry.npmjs.org/cli-interact/-/cli-interact-0.1.9.tgz"
integrity sha512-QHrR2djQOfT1WfHSj9DidBGp+ImgV3t9dTP4cPWTnt9fnOcZqBzCoqd2RysWSIgUXuer5WpmKuT1jqjvwoxdCQ==
dependencies:
readline-sync "~0.4.9"
cli-spinners@^2.5.0: cli-spinners@^2.5.0:
version "2.9.2" version "2.9.2"
resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz" resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz"
@@ -6248,6 +6267,11 @@ readdirp@~3.6.0:
dependencies: dependencies:
picomatch "^2.2.1" picomatch "^2.2.1"
readline-sync@~0.4.9:
version "0.4.10"
resolved "https://registry.npmjs.org/readline-sync/-/readline-sync-0.4.10.tgz"
integrity sha512-q/RyXCZ+49a7octyBMENisHi11JVcnNORLDKEdKEzUnBBiszElOtdCY+wJKPCnwHuh/8+yfDoptGL7D79yUOLw==
real-require@^0.2.0: real-require@^0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz" resolved "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz"