diff --git a/components/prodatakey/actions/open-and-close-device/open-and-close-device.mjs b/components/prodatakey/actions/open-and-close-device/open-and-close-device.mjs new file mode 100644 index 0000000000000..d2532852d5187 --- /dev/null +++ b/components/prodatakey/actions/open-and-close-device/open-and-close-device.mjs @@ -0,0 +1,61 @@ +import prodatakey from "../../prodatakey.app.mjs"; + +export default { + key: "prodatakey-open-and-close-device", + name: "Open and Close Device", + description: "Opens and closes a device in the ProdataKey system. [See the documentation](https://developer.pdk.io/web/2.0/rest/devices#open-and-close-a-device)", + version: "0.0.1", + type: "action", + props: { + prodatakey, + organizationId: { + propDefinition: [ + prodatakey, + "organizationId", + ], + }, + cloudNodeId: { + propDefinition: [ + prodatakey, + "cloudNodeId", + ({ organizationId }) => ({ + organizationId, + }), + ], + }, + deviceId: { + propDefinition: [ + prodatakey, + "deviceId", + ({ + organizationId, cloudNodeId, + }) => ({ + organizationId, + cloudNodeId, + }), + ], + }, + dwell: { + type: "integer", + label: "Dwell", + description: "The amount of time (in tenths of a second) the device will remain open before closing. This value will override the dwell time configured in pdk.io.", + min: 1, + max: 5400, + optional: true, + }, + }, + async run({ $ }) { + const response = await this.prodatakey.openAndCloseDevice({ + $, + organizationId: this.organizationId, + cloudNodeId: this.cloudNodeId, + deviceId: this.deviceId, + data: { + dwell: this.dwell, + }, + }); + + $.export("$summary", `Successfully sent command to device with ID: ${this.deviceId}`); + return response; + }, +}; diff --git a/components/prodatakey/actions/retrieve-credential/retrieve-credential.mjs b/components/prodatakey/actions/retrieve-credential/retrieve-credential.mjs new file mode 100644 index 0000000000000..769139ff923a3 --- /dev/null +++ b/components/prodatakey/actions/retrieve-credential/retrieve-credential.mjs @@ -0,0 +1,50 @@ +import prodatakey from "../../prodatakey.app.mjs"; + +export default { + key: "prodatakey-retrieve-credential", + name: "Retrieve Credential", + description: "Retrieve a specific credential using the system ID, holder ID, and credential ID. [See the documentation](https://developer.pdk.io/web/2.0/rest/credentials)", + version: "0.0.1", + type: "action", + props: { + prodatakey, + organizationId: { + propDefinition: [ + prodatakey, + "organizationId", + ], + }, + holderId: { + propDefinition: [ + prodatakey, + "holderId", + ({ organizationId }) => ({ + organizationId, + }), + ], + }, + credentialId: { + propDefinition: [ + prodatakey, + "credentialId", + ({ + organizationId, holderId, + }) => ({ + organizationId, + holderId, + }), + ], + }, + }, + async run({ $ }) { + const response = await this.prodatakey.retrieveCredential({ + $, + organizationId: this.organizationId, + holderId: this.holderId, + credentialId: this.credentialId, + }); + + $.export("$summary", `Successfully retrieved credential with ID ${response.id}`); + return response; + }, +}; diff --git a/components/prodatakey/actions/retrieve-organization/retrieve-organization.mjs b/components/prodatakey/actions/retrieve-organization/retrieve-organization.mjs new file mode 100644 index 0000000000000..0f11aaaa2a103 --- /dev/null +++ b/components/prodatakey/actions/retrieve-organization/retrieve-organization.mjs @@ -0,0 +1,27 @@ +import prodatakey from "../../prodatakey.app.mjs"; + +export default { + key: "prodatakey-retrieve-organization", + name: "Retrieve Organization", + description: "Retrieve an existing organization. [See the documentation](https://developer.pdk.io/web/2.0/rest/organizations#retrieve-an-organization)", + version: "0.0.1", + type: "action", + props: { + prodatakey, + organizationId: { + propDefinition: [ + prodatakey, + "organizationId", + ], + }, + }, + async run({ $ }) { + const response = await this.prodatakey.retrieveOrganization({ + $, + organizationId: this.organizationId, + }); + + $.export("$summary", `Successfully retrieved organization: ${response.name}`); + return response; + }, +}; diff --git a/components/prodatakey/common/constants.mjs b/components/prodatakey/common/constants.mjs new file mode 100644 index 0000000000000..2b4caa24d0099 --- /dev/null +++ b/components/prodatakey/common/constants.mjs @@ -0,0 +1,127 @@ +export const EVENT_OPTIONS = +[ + { + label: "Triggers when a controller's relay circuits stabilize.", + value: "device.alarm.circuitbreaker.off", + }, + { + label: "Triggers when a controller's relay circuits become unstable (e.g. due to loose wires).", + value: "device.alarm.circuitbreaker.on", + }, + { + label: "Triggers when a device equipped with a door position sensor (DPS) is forced open.", + value: "device.alarm.forced", + }, + { + label: "Triggers when a forced alarm has cleared.", + value: "device.alarm.forced.cleared", + }, + { + label: "Triggers when prop alarms are cleared for all devices.", + value: "device.alarm.propped.alloff", + }, + { + label: "Triggers when a prop alarm has cleared for a device.", + value: "device.alarm.propped.off", + }, + { + label: "Triggers when a device equipped with a door position sensor (DPS) is propped open.", + value: "device.alarm.propped.on", + }, + { + label: "Triggers when auto open is deactivated.", + value: "device.autoopen.off", + }, + { + label: "Triggers when auto open is activated.", + value: "device.autoopen.on", + }, + { + label: "Triggers when an auto open override is deactivated.", + value: "device.autoopen.override.off", + }, + { + label: "Triggers when an auto open override is activated.", + value: "device.autoopen.override.on", + }, + { + label: "Triggers when do not disturb (DND) is deactivated.", + value: "device.forceclose.off", + }, + { + label: "Triggers when do not disturb (DND) is activated.", + value: "device.forceclose.on", + }, + { + label: "Triggers when force unlock is deactivated.", + value: "device.forceopen.off", + }, + { + label: "Triggers when force unlock is activated.", + value: "device.forceopen.on", + }, + { + label: "Triggers when a door position sensor (DPS) changes to a closed state.", + value: "device.input.dps.closed", + }, + { + label: "Triggers when a door position sensor (DPS) changes to an open state.", + value: "device.input.dps.opened", + }, + { + label: "Triggers when a device is locked.", + value: "device.input.relay.off", + }, + { + label: "Triggers when a device is unlocked.", + value: "device.input.relay.on", + }, + { + label: "Triggers when the request to exit (REX) is deactivated.", + value: "device.input.rex.off", + }, + { + label: "Triggers when the request to exit (REX) is activated.", + value: "device.input.rex.on", + }, + { + label: "Triggers when a credential scan is emulated on a device (e.g. when opening a device using a software button).", + value: "device.input.virtualread", + }, + { + label: "Triggers when a holder is recognized and granted access.", + value: "device.request.allowed", + }, + { + label: "Triggers when a holder is recognized but denied access.", + value: "device.request.denied", + }, + { + label: "Triggers when a holder enters a duress PIN.", + value: "device.request.duress", + }, + { + label: "Triggers when access is granted using an emergency card. Since emergency cards can be used while a system is offline, these events may be delayed.", + value: "device.request.ecard.allowed", + }, + { + label: "Triggers when access is denied using an emergency card. Since emergency cards can be used while a system is offline, these events may be delayed.", + value: "device.request.ecard.denied", + }, + { + label: "Triggers when a holder is recognized.", + value: "device.request.found", + }, + { + label: "Triggers when a credential is denied due to facility code filtering.", + value: "device.request.filtered", + }, + { + label: "Triggers when a holder is granted access after multiple sequential scans. This behavior may be used to configure other actions, such as activating or deactivating an alarm system.", + value: "device.request.multiallowed", + }, + { + label: "Triggers when a credential is presented but the holder is not recognized.", + value: "device.request.unknown", + }, +]; diff --git a/components/prodatakey/package.json b/components/prodatakey/package.json index e6d828f157bb2..8aff569355976 100644 --- a/components/prodatakey/package.json +++ b/components/prodatakey/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/prodatakey", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream ProdataKey Components", "main": "prodatakey.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.1.0" } -} \ No newline at end of file +} diff --git a/components/prodatakey/prodatakey.app.mjs b/components/prodatakey/prodatakey.app.mjs index d26f7cf3ec95f..bb48735339d86 100644 --- a/components/prodatakey/prodatakey.app.mjs +++ b/components/prodatakey/prodatakey.app.mjs @@ -1,11 +1,257 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "prodatakey", - propDefinitions: {}, + propDefinitions: { + organizationId: { + type: "string", + label: "Organization ID", + description: "The ID of the organization.", + async options() { + const organizations = await this.listOrganizations(); + return organizations.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + holderId: { + type: "string", + label: "Holder ID", + description: "The ID of the holder.", + async options({ organizationId }) { + const holders = await this.listHolders({ + organizationId, + }); + + return holders.map(({ + id: value, firstName, lastName, email, + }) => ({ + label: `${firstName} ${lastName} (${email})`, + value, + })); + }, + }, + credentialId: { + type: "string", + label: "Credential ID", + description: "The ID of the credential to retrieve.", + async options({ + organizationId, holderId, + }) { + const credentials = await this.listCredentials({ + organizationId, + holderId, + }); + + return credentials.map(({ + id: value, description: label, + }) => ({ + label, + value, + })); + }, + }, + cloudNodeId: { + type: "string", + label: "Cloud Node ID", + description: "The ID of the cloud node.", + async options({ organizationId }) { + const cloudNodes = await this.listCloudNodes({ + organizationId, + }); + + return cloudNodes.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + deviceId: { + type: "string", + label: "Device ID", + description: "The ID of the device to retrieve.", + async options({ + organizationId, cloudNodeId, + }) { + const devices = await this.listDevices({ + organizationId, + cloudNodeId, + }); + + return devices.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + dealerId: { + type: "string", + label: "Dealer ID", + description: "The ID of the dealer to retrieve.", + async options() { + const dealers = await this.listDealers(); + return dealers.map((dealer) => ({ + label: dealer.name, + value: dealer.id, + })); + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl(systemId = null) { + return `https://${systemId + ? "systems" + : "accounts"}.pdk.io`; + }, + async _headers({ systemId = null }) { + if (systemId) { + const { token } = await this.getSystemToken({ + systemId, + }); + return { + Authorization: `Bearer ${token}`, + }; + } + + return { + Authorization: `Bearer ${this.$auth.id_token}`, + }; + }, + async _makeRequest({ + $ = this, path, systemId, ...opts + }) { + return axios($, { + url: this._baseUrl(systemId) + path, + headers: await this._headers({ + systemId, + }), + ...opts, + }); + }, + getSystemToken({ + systemId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/api/systems/${systemId}/token`, + ...opts, + }); + }, + listOrganizations(opts = {}) { + return this._makeRequest({ + path: "/api/organizations/mine", + ...opts, + }); + }, + retrieveOrganization({ + organizationId, ...opts + }) { + return this._makeRequest({ + path: `/api/organizations/${organizationId}`, + ...opts, + }); + }, + async listHolders({ + organizationId, ...opts + }) { + const { systemId } = await this.retrieveOrganization({ + organizationId, + }); + return this._makeRequest({ + path: `/${systemId}/holders`, + ...opts, + }); + }, + async listCredentials({ + organizationId, holderId, ...opts + }) { + const { systemId } = await this.retrieveOrganization({ + organizationId, + }); + return this._makeRequest({ + path: `/${systemId}/holders/${holderId}/credentials`, + systemId, + ...opts, + }); + }, + async retrieveCredential({ + organizationId, holderId, credentialId, + }) { + const { systemId } = await this.retrieveOrganization({ + organizationId, + }); + return this._makeRequest({ + path: `/${systemId}/holders/${holderId}/credentials/${credentialId}`, + systemId, + }); + }, + listDealers(opts = {}) { + return this._makeRequest({ + path: "/api/dealers", + ...opts, + }); + }, + async listCloudNodes({ + organizationId, ...opts + }) { + const { systemId } = await this.retrieveOrganization({ + organizationId, + }); + return this._makeRequest({ + path: `/${systemId}/cloud-nodes`, + systemId, + ...opts, + }); + }, + async listDevices({ + organizationId, cloudNodeId, ...opts + }) { + const { systemId } = await this.retrieveOrganization({ + organizationId, + }); + return this._makeRequest({ + path: `/${systemId}/cloud-nodes/${cloudNodeId}/devices`, + systemId, + ...opts, + }); + }, + async openAndCloseDevice({ + organizationId, cloudNodeId, deviceId, data, + }) { + const { systemId } = await this.retrieveOrganization({ + organizationId, + }); + return this._makeRequest({ + method: "POST", + path: `/${systemId}/cloud-nodes/${cloudNodeId}/devices/${deviceId}/try-open`, + systemId, + data, + }); + }, + createHook({ + organizationId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/api/organizations/${organizationId}/subscriptions`, + ...opts, + }); + }, + deleteHook({ + organizationId, webhookId, + }) { + return this._makeRequest({ + method: "DELETE", + path: `/api/organizations/${organizationId}/subscriptions/${webhookId}`, + }); }, }, -}; \ No newline at end of file +}; diff --git a/components/prodatakey/sources/common/base.mjs b/components/prodatakey/sources/common/base.mjs new file mode 100644 index 0000000000000..8f35de58bf649 --- /dev/null +++ b/components/prodatakey/sources/common/base.mjs @@ -0,0 +1,47 @@ +import prodatakey from "../../prodatakey.app.mjs"; + +export default { + props: { + prodatakey, + db: "$.service.db", + http: "$.interface.http", + organizationId: { + propDefinition: [ + prodatakey, + "organizationId", + ], + }, + name: { + type: "string", + label: "Webhook Name", + description: "The name of the webhook", + }, + }, + hooks: { + async activate() { + const response = await this.prodatakey.createHook({ + organizationId: this.organizationId, + data: { + name: this.name, + url: this.http.endpoint, + scope: "this", + authentication: { + type: "None", + }, + events: this.getEvent(), + }, + }); + this.db.set("webhookId", response.id); + }, + async deactivate() { + const webhookId = this.db.get("webhookId"); + await this.prodatakey.deleteHook({ + organizationId: this.organizationId, + webhookId, + }); + }, + }, + async run({ body }) { + this.$emit(body, this.generateMeta(body)); + }, +}; diff --git a/components/prodatakey/sources/new-event-instant/new-event-instant.mjs b/components/prodatakey/sources/new-event-instant/new-event-instant.mjs new file mode 100644 index 0000000000000..2446122c786f8 --- /dev/null +++ b/components/prodatakey/sources/new-event-instant/new-event-instant.mjs @@ -0,0 +1,37 @@ +import { EVENT_OPTIONS } from "../../common/constants.mjs"; +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "prodatakey-new-event-instant", + name: "New Event (Instant)", + description: "Emit new event when an event is triggered. [See the documentation](https://developer.pdk.io/web/2.0/rest/webhooks/#introduction)", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + event: { + type: "string", + label: "Event", + description: "The event to listen for", + options: EVENT_OPTIONS, + }, + }, + methods: { + getEvent() { + return [ + this.event, + ]; + }, + generateMeta(event) { + return { + id: event.id, + summary: `Event triggered for ${event.cloudNodeSN}`, + ts: event.occurred.occurred || Date.now(), + }; + }, + }, + sampleEmit, +}; diff --git a/components/prodatakey/sources/new-event-instant/test-event.mjs b/components/prodatakey/sources/new-event-instant/test-event.mjs new file mode 100644 index 0000000000000..0ae7b626f7600 --- /dev/null +++ b/components/prodatakey/sources/new-event-instant/test-event.mjs @@ -0,0 +1,21 @@ +export default { + "body": { + "deviceId": "1bf7a641-4b1b-4d6c-9c57-818e7515575d", + "facilityCode": "123", + "holderId": "8129a254-dc3d-419f-b7e9-4ad772afd23f", + "requestFactor": "CARD_ONLY" + }, + "cloudNodeId": "91c2a7b7-28e9-4dec-e1f7-9966b1d3ca55", + "cloudNodeSN": "1234ABC", + "id": "90163e3c-a31f-4bc5-9f6f-88eea9fc5b6c", + "metadata": { + "deviceName": "Test Device", + "holderName": "John Wiegand", + "occurred": 1710201553630, + "offset": -14400000, + "rules": [], + "source": "board", + "systemId": "2b6c14b2-ce6d-4109-ab4e-b95a65b048d3" + }, + "topic": "device.request.allowed" +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cae0d57aeca40..1be4ed2cc7057 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -751,8 +751,7 @@ importers: components/amazon_polly: {} - components/amazon_redshift: - specifiers: {} + components/amazon_redshift: {} components/amazon_selling_partner: {} @@ -10448,7 +10447,11 @@ importers: components/procore_sandbox: {} - components/prodatakey: {} + components/prodatakey: + dependencies: + '@pipedream/platform': + specifier: ^3.1.0 + version: 3.1.0 components/prodpad: dependencies: @@ -44039,7 +44042,7 @@ snapshots: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.7.1 + semver: 7.7.2 transitivePeerDependencies: - supports-color