diff --git a/components/onlyoffice_docspace/actions/create-room/create-room.mjs b/components/onlyoffice_docspace/actions/create-room/create-room.mjs new file mode 100644 index 0000000000000..c516e4ee180ef --- /dev/null +++ b/components/onlyoffice_docspace/actions/create-room/create-room.mjs @@ -0,0 +1,77 @@ +import app from "../../onlyoffice_docspace.app.mjs"; + +export default { + key: "onlyoffice_docspace-create-room", + name: "Create Room", + description: "Creates a new room. [See the documentation](https://api.onlyoffice.com/docspace/method/files/post/api/2.0/files/rooms)", + version: "0.0.1", + type: "action", + props: { + app, + title: { + type: "string", + label: "Title", + description: "The name of the room to be created.", + }, + roomType: { + type: "integer", + label: "Room Type", + description: "The type of the room.", + options: [ + { + label: "Editing Room", + value: 2, + }, + { + label: "Custom Room", + value: 5, + }, + { + label: "Public Room", + value: 6, + }, + ], + }, + notify: { + type: "boolean", + label: "Notify", + description: "Whether to notify the user about the room creation.", + optional: true, + }, + sharingMessage: { + type: "string", + label: "Sharing Message", + description: "Message to send when notifying about the shared room", + optional: true, + }, + }, + methods: { + createRoom(args = {}) { + return this.app.post({ + path: "/files/rooms", + ...args, + }); + }, + }, + async run({ $ }) { + const { + createRoom, + title, + roomType, + notify, + sharingMessage, + } = this; + + const response = await createRoom({ + $, + data: { + Title: title, + RoomType: roomType, + Notify: notify, + SharingMessage: sharingMessage, + }, + }); + $.export("$summary", `Successfully created room with ID \`${response.response.id}\`.`); + return response; + }, +}; diff --git a/components/onlyoffice_docspace/actions/invite-user/invite-user.mjs b/components/onlyoffice_docspace/actions/invite-user/invite-user.mjs new file mode 100644 index 0000000000000..2fed95ae39ff1 --- /dev/null +++ b/components/onlyoffice_docspace/actions/invite-user/invite-user.mjs @@ -0,0 +1,45 @@ +import app from "../../onlyoffice_docspace.app.mjs"; + +export default { + key: "onlyoffice_docspace-invite-user", + name: "Invite User", + description: "Invites a new user to the portal. [See the documentation](https://api.onlyoffice.com/docspace/method/people/post/api/2.0/people/invite)", + version: "0.0.1", + type: "action", + props: { + app, + email: { + type: "string", + label: "User Email", + description: "The email of the user to invite.", + }, + }, + methods: { + inviteUser(args = {}) { + return this.app.post({ + path: "/people/invite", + ...args, + }); + }, + }, + async run({ $ }) { + const { + inviteUser, + email, + } = this; + + const response = await inviteUser({ + $, + data: { + Invitations: [ + { + email, + }, + ], + }, + }); + + $.export("$summary", `Successfully invited user with email \`${email}\``); + return response; + }, +}; diff --git a/components/onlyoffice_docspace/actions/upload-file/upload-file.mjs b/components/onlyoffice_docspace/actions/upload-file/upload-file.mjs new file mode 100644 index 0000000000000..e1dd3512f196f --- /dev/null +++ b/components/onlyoffice_docspace/actions/upload-file/upload-file.mjs @@ -0,0 +1,55 @@ +import app from "../../onlyoffice_docspace.app.mjs"; +import constants from "../../common/constants.mjs"; + +export default { + key: "onlyoffice_docspace-upload-file", + name: "Upload File", + description: "Uploads a file to the specified room. [See the documentation](https://api.onlyoffice.com/docspace/method/files/post/api/2.0/files/%7bfolderid%7d/upload)", + version: "0.0.1", + type: "action", + props: { + app, + folderId: { + label: "Folder ID", + description: "The ID of the folder where you want the file to be uploaded.", + propDefinition: [ + app, + "file", + ], + }, + file: { + type: "string", + label: "File", + description: "File path of a file previously downloaded in Pipedream E.g. (`/tmp/my-file.txt`). [Download a file to the `/tmp` directory](https://pipedream.com/docs/code/nodejs/http-requests/#download-a-file-to-the-tmp-directory)", + }, + }, + methods: { + uploadFile({ + folderId, ...args + } = {}) { + return this.app.post({ + path: `/files/${folderId}/upload`, + ...args, + }); + }, + }, + async run({ $ }) { + const { + uploadFile, + folderId, + file, + } = this; + + const response = await uploadFile({ + $, + folderId, + headers: constants.MULTIPART_FORM_DATA_HEADERS, + data: { + File: file, + }, + }); + + $.export("$summary", `Successfully uploaded file with ID \`${response.response.id}\`.`); + return response; + }, +}; diff --git a/components/onlyoffice_docspace/common/constants.mjs b/components/onlyoffice_docspace/common/constants.mjs new file mode 100644 index 0000000000000..6ace84c5f9959 --- /dev/null +++ b/components/onlyoffice_docspace/common/constants.mjs @@ -0,0 +1,49 @@ +const SUBDOMAIN_PLACEHOLDER = "{subdomain}"; +const VERSION_PATH = "/api/2.0"; +const BASE_URL = `https://${SUBDOMAIN_PLACEHOLDER}.onlyoffice.com${VERSION_PATH}`; + +const FILE_PROP_NAMES = [ + "File", +]; + +const CONTENT_TYPE_KEY_HEADER = "Content-Type"; +const MULTIPART_FORM_DATA_VALUE_HEADER = "multipart/form-data"; +const MULTIPART_FORM_DATA_HEADERS = { + [CONTENT_TYPE_KEY_HEADER]: MULTIPART_FORM_DATA_VALUE_HEADER, +}; + +const RESOURCE_NAME = { + FILES: "files", + FOLDERS: "folders", +}; + +const FILTER_TYPE = { + NONE: "None", + FILES_ONLY: "FilesOnly", + FOLDERS_ONLY: "FoldersOnly", + DOCUMENTS_ONLY: "DocumentsOnly", + PRESENTATIONS_ONLY: "PresentationsOnly", + SPREADSHEETS_ONLY: "SpreadsheetsOnly", + IMAGES_ONLY: "ImagesOnly", + BY_USER: "ByUser", + BY_DEPARTMENT: "ByDepartment", + ARCHIVE_ONLY: "ArchiveOnly", + BY_EXTENSION: "ByExtension", + MEDIA_ONLY: "MediaOnly", + EDITING_ROOMS: "EditingRooms", + CUSTOM_ROOMS: "CustomRooms", + OFORM_TEMPLATE_ONLY: "OFormTemplateOnly", + OFORM_ONLY: "OFormOnly", +}; + +export default { + SUBDOMAIN_PLACEHOLDER, + BASE_URL, + VERSION_PATH, + RESOURCE_NAME, + FILTER_TYPE, + FILE_PROP_NAMES, + MULTIPART_FORM_DATA_VALUE_HEADER, + MULTIPART_FORM_DATA_HEADERS, + CONTENT_TYPE_KEY_HEADER, +}; diff --git a/components/onlyoffice_docspace/common/utils.mjs b/components/onlyoffice_docspace/common/utils.mjs new file mode 100644 index 0000000000000..a4710a240ed9f --- /dev/null +++ b/components/onlyoffice_docspace/common/utils.mjs @@ -0,0 +1,40 @@ +import { createReadStream } from "fs"; +import FormData from "form-data"; +import constants from "./constants.mjs"; + +function buildFormData(formData, data, parentKey) { + if (data && typeof(data) === "object") { + Object.keys(data) + .forEach((key) => { + buildFormData(formData, data[key], parentKey && `${parentKey}[${key}]` || key); + }); + + } else if (data && constants.FILE_PROP_NAMES.some((prop) => parentKey.includes(prop))) { + formData.append(parentKey, createReadStream(data)); + + } else if (data) { + formData.append(parentKey, (data).toString()); + } +} + +function getFormData(data) { + try { + const formData = new FormData(); + buildFormData(formData, data); + return formData; + } catch (error) { + console.log("FormData Error", error); + throw error; + } +} + +function hasMultipartHeader(headers) { + return headers + && headers[constants.CONTENT_TYPE_KEY_HEADER]?. + includes(constants.MULTIPART_FORM_DATA_VALUE_HEADER); +} + +export default { + getFormData, + hasMultipartHeader, +}; diff --git a/components/onlyoffice_docspace/onlyoffice_docspace.app.mjs b/components/onlyoffice_docspace/onlyoffice_docspace.app.mjs index 05855017aa5d3..a2380211af035 100644 --- a/components/onlyoffice_docspace/onlyoffice_docspace.app.mjs +++ b/components/onlyoffice_docspace/onlyoffice_docspace.app.mjs @@ -1,11 +1,97 @@ +import { axios } from "@pipedream/platform"; +import constants from "./common/constants.mjs"; +import utils from "./common/utils.mjs"; + export default { type: "app", app: "onlyoffice_docspace", - propDefinitions: {}, + propDefinitions: { + file: { + type: "string", + label: "File", + description: "The file or folder ID.", + async options({ + resourcesName = constants.RESOURCE_NAME.FOLDERS, + mapper = ({ + id, + title: label, + }) => ({ + value: String(id), + label, + }), + params = { + filterType: constants.FILTER_TYPE.FOLDERS_ONLY, + withsubfolders: true, + }, + }) { + const { response: { [resourcesName]: resources } } = + await this.listMyFilesAndFolders({ + params, + }); + return resources.map(mapper); + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + getUrl(path) { + const baseUrl = constants.BASE_URL + .replace(constants.SUBDOMAIN_PLACEHOLDER, this.$auth.subdomain); + return `${baseUrl}${path}`; + }, + getHeaders(headers) { + return { + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": this.$auth.oauth_access_token, + ...headers, + }; + }, + getConfig({ + headers, data: preData, ...args + } = {}) { + const contentType = constants.CONTENT_TYPE_KEY_HEADER; + const hasMultipartHeader = utils.hasMultipartHeader(headers); + const data = hasMultipartHeader && utils.getFormData(preData) || preData; + const currentHeaders = this.getHeaders(headers); + + return { + headers: hasMultipartHeader + ? { + ...currentHeaders, + [contentType]: data.getHeaders()[contentType.toLowerCase()], + } + : currentHeaders, + data, + ...args, + }; + }, + _makeRequest({ + $ = this, path, headers, ...args + } = {}) { + const config = this.getConfig({ + url: this.getUrl(path), + headers: this.getHeaders(headers), + ...args, + }); + return axios($, config); + }, + post(args = {}) { + return this._makeRequest({ + method: "POST", + ...args, + }); + }, + listMyFilesAndFolders(args = {}) { + return this._makeRequest({ + path: "/files/@my", + ...args, + }); + }, + searchUsersByExtendedFilter(args = {}) { + return this._makeRequest({ + path: "/people/simple/filter", + ...args, + }); }, }, -}; \ No newline at end of file +}; diff --git a/components/onlyoffice_docspace/package.json b/components/onlyoffice_docspace/package.json index 4188535072abe..cab0756882afd 100644 --- a/components/onlyoffice_docspace/package.json +++ b/components/onlyoffice_docspace/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/onlyoffice_docspace", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream ONLYOFFICE DocSpace Components", "main": "onlyoffice_docspace.app.mjs", "keywords": [ @@ -11,5 +11,9 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.0", + "form-data": "^4.0.0" } -} \ No newline at end of file +} diff --git a/components/onlyoffice_docspace/sources/common/polling.mjs b/components/onlyoffice_docspace/sources/common/polling.mjs new file mode 100644 index 0000000000000..916900823eac9 --- /dev/null +++ b/components/onlyoffice_docspace/sources/common/polling.mjs @@ -0,0 +1,72 @@ +import { + ConfigurationError, + DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, +} from "@pipedream/platform"; +import app from "../../onlyoffice_docspace.app.mjs"; + +export default { + props: { + app, + timer: { + type: "$.interface.timer", + label: "Polling Schedule", + description: "How often to poll the API", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + methods: { + generateMeta() { + throw new ConfigurationError("generateMeta is not implemented"); + }, + sortFn() { + return; + }, + getResourcesName() { + return; + }, + getResourcesFn() { + throw new ConfigurationError("getResourcesFn is not implemented"); + }, + getResourcesFnArgs() { + throw new ConfigurationError("getResourcesFnArgs is not implemented"); + }, + processResource(resource) { + const meta = this.generateMeta(resource); + this.$emit(resource, meta); + }, + async processResources(resources) { + const { + sortFn, + processResource, + } = this; + + Array.from(resources) + .sort(sortFn) + .forEach(processResource); + }, + }, + async run() { + const { + getResourcesFn, + getResourcesFnArgs, + getResourcesName, + processResources, + } = this; + + const resourcesName = getResourcesName(); + const resourcesFn = getResourcesFn(); + const metadata = await resourcesFn(getResourcesFnArgs()); + + let resources; + + if (resourcesName) { + ({ response: { [resourcesName]: resources } } = metadata); + } else { + ({ response: resources } = metadata); + } + + processResources(resources); + }, +}; diff --git a/components/onlyoffice_docspace/sources/new-file/new-file.mjs b/components/onlyoffice_docspace/sources/new-file/new-file.mjs new file mode 100644 index 0000000000000..66deec4dc805a --- /dev/null +++ b/components/onlyoffice_docspace/sources/new-file/new-file.mjs @@ -0,0 +1,40 @@ +import constants from "../../common/constants.mjs"; +import common from "../common/polling.mjs"; + +export default { + ...common, + key: "onlyoffice_docspace-new-file", + name: "New File Created", + description: "Emit new event when a new file is created. [See the documentation](https://api.onlyoffice.com/docspace/method/files/get/api/2.0/files/%40root).", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + sortFn(a, b) { + return new Date(b.created) - new Date(a.created); + }, + getResourcesName() { + return constants.RESOURCE_NAME.FILES; + }, + getResourcesFn() { + return this.app.listMyFilesAndFolders; + }, + getResourcesFnArgs() { + return { + debug: true, + params: { + filterType: constants.FILTER_TYPE.FILES_ONLY, + withsubfolders: true, + }, + }; + }, + generateMeta(resource) { + return { + id: resource.id, + summary: `New File: ${resource.title}`, + ts: Date.parse(resource.created), + }; + }, + }, +}; diff --git a/components/onlyoffice_docspace/sources/new-folder/new-folder.mjs b/components/onlyoffice_docspace/sources/new-folder/new-folder.mjs new file mode 100644 index 0000000000000..d03ab41d05fd4 --- /dev/null +++ b/components/onlyoffice_docspace/sources/new-folder/new-folder.mjs @@ -0,0 +1,40 @@ +import constants from "../../common/constants.mjs"; +import common from "../common/polling.mjs"; + +export default { + ...common, + key: "onlyoffice_docspace-new-folder", + name: "New Folder Created", + description: "Emit new event when a new folder is createdr. [See the documentation](https://api.onlyoffice.com/docspace/method/files/get/api/2.0/files/%40root).", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + sortFn(a, b) { + return new Date(b.created) - new Date(a.created); + }, + getResourcesName() { + return constants.RESOURCE_NAME.FOLDERS; + }, + getResourcesFn() { + return this.app.listMyFilesAndFolders; + }, + getResourcesFnArgs() { + return { + debug: true, + params: { + filterType: constants.FILTER_TYPE.FOLDERS_ONLY, + withsubfolders: true, + }, + }; + }, + generateMeta(resource) { + return { + id: resource.id, + summary: `New Folder: ${resource.title}`, + ts: Date.parse(resource.created), + }; + }, + }, +}; diff --git a/components/onlyoffice_docspace/sources/new-user-added/new-user-added.mjs b/components/onlyoffice_docspace/sources/new-user-added/new-user-added.mjs new file mode 100644 index 0000000000000..38bb100b994fa --- /dev/null +++ b/components/onlyoffice_docspace/sources/new-user-added/new-user-added.mjs @@ -0,0 +1,32 @@ +import common from "../common/polling.mjs"; + +export default { + ...common, + key: "onlyoffice_docspace-new-user-added", + name: "New User Added", + description: "Emit new event when a new user is added. [See the documentation](https://api.onlyoffice.com/docspace/method/people/get/api/2.0/people/simple/filter).", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getResourcesFn() { + return this.app.searchUsersByExtendedFilter; + }, + getResourcesFnArgs() { + return { + debug: true, + params: { + activationStatus: "Activated", + }, + }; + }, + generateMeta(resource) { + return { + id: resource.id, + summary: `New User: ${resource.displayName}`, + ts: Date.now(), + }; + }, + }, +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1cae9e289235b..9d92e59c0e407 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6261,7 +6261,12 @@ importers: ongage: 1.1.7_node-fetch@2.7.0 components/onlyoffice_docspace: - specifiers: {} + specifiers: + '@pipedream/platform': ^3.0.0 + form-data: ^4.0.0 + dependencies: + '@pipedream/platform': 3.0.0 + form-data: 4.0.0 components/onstrategy: specifiers: