diff --git a/components/nile_database/actions/create-user/create-user.mjs b/components/nile_database/actions/create-user/create-user.mjs new file mode 100644 index 0000000000000..dcaf2e06c2c02 --- /dev/null +++ b/components/nile_database/actions/create-user/create-user.mjs @@ -0,0 +1,54 @@ +import nile from "../../nile_database.app.mjs"; + +export default { + key: "nile_database-create-user", + name: "Create User", + description: "Create a new database user by providing an email address and password. [See the documentation](https://www.thenile.dev/docs/reference/api-reference/users/create-user)", + version: "0.0.1", + type: "action", + props: { + nile, + workspace: { + propDefinition: [ + nile, + "workspace", + ], + }, + database: { + propDefinition: [ + nile, + "database", + ], + }, + email: { + type: "string", + label: "Email", + description: "Email address of the user", + }, + password: { + type: "string", + label: "Password", + description: "Password for the user", + }, + preferredName: { + type: "string", + label: "Preferred Name", + description: "The preferred name of the user", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.nile.createUser({ + $, + workspace: this.workspace, + database: this.database, + data: { + email: this.email, + password: this.password, + preferredName: this.preferredName, + }, + }); + $.export("$summary", `Successfully created user with ID: ${response.id}`); + return response; + }, +}; diff --git a/components/nile_database/actions/execute-query/execute-query.mjs b/components/nile_database/actions/execute-query/execute-query.mjs new file mode 100644 index 0000000000000..1b8c0ca926268 --- /dev/null +++ b/components/nile_database/actions/execute-query/execute-query.mjs @@ -0,0 +1,56 @@ +import nile from "../../nile_database.app.mjs"; + +export default { + name: "Execute Query", + key: "nile_database-execute-query", + description: "Execute a custom PostgreSQL query.", + version: "0.0.1", + type: "action", + props: { + nile, + workspace: { + propDefinition: [ + nile, + "workspace", + ], + }, + database: { + propDefinition: [ + nile, + "database", + ], + }, + user: { + type: "string", + label: "Username", + description: "The username or userId of the database user. Note: Credentials are generated in the Nile Dashboard under Settings -> Credentials", + }, + password: { + type: "string", + label: "Password", + description: "The password of the database user", + }, + query: { + type: "string", + label: "Query", + description: "The PostgreSQL query to execute", + }, + }, + async run({ $ }) { + const config = { + user: this.user, + password: this.password, + host: await this.nile.getHost({ + workspace: this.workspace, + database: this.database, + }), + port: "5432", + database: this.database, + }; + const data = await this.nile.executeQuery(config, this.query); + $.export("$summary", `Returned ${data.length} ${data.length === 1 + ? "row" + : "rows"}`); + return data; + }, +}; diff --git a/components/nile_database/nile_database.app.mjs b/components/nile_database/nile_database.app.mjs index cb85e6227fca2..0bad4b5983bc6 100644 --- a/components/nile_database/nile_database.app.mjs +++ b/components/nile_database/nile_database.app.mjs @@ -1,11 +1,137 @@ +import { axios } from "@pipedream/platform"; +import pg from "pg"; + export default { type: "app", app: "nile_database", - propDefinitions: {}, + propDefinitions: { + workspace: { + type: "string", + label: "Workspace", + description: "Your workspace slug", + async options() { + const { workspaces } = await this.getAuthenticatedUser(); + return workspaces?.map(({ slug }) => slug) || []; + }, + }, + database: { + type: "string", + label: "Database", + description: "The database name", + async options() { + const { databases } = await this.getAuthenticatedUser(); + return databases?.map(({ name }) => name) || []; + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _globalBaseUrl() { + return "https://global.thenile.dev"; + }, + async _getBaseUrl({ + workspace, database, ...opts + }) { + const { apiHost } = await this.getDatabase({ + workspace, + database, + ...opts, + }); + return apiHost; + }, + async _makeRequest({ + $ = this, + workspace, + database, + url, + path, + ...opts + }) { + return axios($, { + url: url || `${await this._getBaseUrl({ + workspace, + database, + $, + })}${path}`, + headers: { + Authorization: `Bearer ${this.$auth.oauth_access_token}`, + }, + ...opts, + }); + }, + getDatabase({ + workspace, database, ...opts + }) { + return this._makeRequest({ + url: `${this._globalBaseUrl()}/workspaces/${workspace}/databases/${database}`, + workspace, + database, + ...opts, + }); + }, + getAuthenticatedUser(opts = {}) { + return this._makeRequest({ + url: `${this._globalBaseUrl()}/developers/me/full`, + ...opts, + }); + }, + async getHost({ + workspace, database, ...opts + }) { + const { dbHost } = await this.getDatabase({ + workspace, + database, + ...opts, + }); + const host = dbHost.match(/postgres:\/\/([^/]+)\//); + return host[1]; + }, + listUsers({ + workspace, database, ...opts + }) { + return this._makeRequest({ + path: "/users", + workspace, + database, + ...opts, + }); + }, + listTenants({ + workspace, database, ...opts + }) { + return this._makeRequest({ + path: "/tenants", + workspace, + database, + ...opts, + }); + }, + createUser({ + workspace, database, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: "/users", + workspace, + database, + ...opts, + }); + }, + async _getClient(config) { + const pool = new pg.Pool(config); + const client = await pool.connect(); + return client; + }, + async _endClient(client) { + return client.release(); + }, + async executeQuery(config, query) { + const client = await this._getClient(config); + try { + const { rows } = await client.query(query); + return rows; + } finally { + await this._endClient(client); + } }, }, -}; \ No newline at end of file +}; diff --git a/components/nile_database/package.json b/components/nile_database/package.json index a93f49b6bc770..1d24c129fc4a7 100644 --- a/components/nile_database/package.json +++ b/components/nile_database/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/nile_database", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Nile Database Components", "main": "nile_database.app.mjs", "keywords": [ @@ -11,5 +11,9 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3", + "pg": "^8.13.0" } -} \ No newline at end of file +} diff --git a/components/nile_database/sources/common/base.mjs b/components/nile_database/sources/common/base.mjs new file mode 100644 index 0000000000000..9d43dfb69515b --- /dev/null +++ b/components/nile_database/sources/common/base.mjs @@ -0,0 +1,48 @@ +import nile from "../../nile_database.app.mjs"; +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; + +export default { + props: { + nile, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + workspace: { + propDefinition: [ + nile, + "workspace", + ], + }, + database: { + propDefinition: [ + nile, + "database", + ], + }, + }, + methods: { + getResourceFn() { + throw new Error("getResourceFn is not implemented"); + }, + generateMeta() { + throw new Error("generateMeta is not implemented"); + }, + }, + async run() { + const resourceFn = this.getResourceFn(); + + const results = await resourceFn({ + workspace: this.workspace, + database: this.database, + }); + + for (const item of results) { + const meta = this.generateMeta(item); + this.$emit(item, meta); + } + }, +}; diff --git a/components/nile_database/sources/new-tenant-created/new-tenant-created.mjs b/components/nile_database/sources/new-tenant-created/new-tenant-created.mjs new file mode 100644 index 0000000000000..fb0cd1c0249e9 --- /dev/null +++ b/components/nile_database/sources/new-tenant-created/new-tenant-created.mjs @@ -0,0 +1,24 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "nile_database-new-tenant-created", + name: "New Tenant Created", + description: "Emit new event when a new tenant is added to a Nile Database", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getResourceFn() { + return this.nile.listTenants; + }, + generateMeta(tenant) { + return { + id: tenant.id, + summary: `New Tenant ID: ${tenant.id}`, + ts: Date.now(), + }; + }, + }, +}; diff --git a/components/nile_database/sources/new-user-created/new-user-created.mjs b/components/nile_database/sources/new-user-created/new-user-created.mjs new file mode 100644 index 0000000000000..2e7f3b6c29007 --- /dev/null +++ b/components/nile_database/sources/new-user-created/new-user-created.mjs @@ -0,0 +1,24 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "nile_database-new-user-created", + name: "New User Created", + description: "Emit new event when a new user is added in a Nile Database", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getResourceFn() { + return this.nile.listUsers; + }, + generateMeta(user) { + return { + id: user.id, + summary: `New User ID: ${user.id}`, + ts: Date.parse(user.created), + }; + }, + }, +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 93c26d6d11352..a52b9441bbaa3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6438,7 +6438,12 @@ importers: specifiers: {} components/nile_database: - specifiers: {} + specifiers: + '@pipedream/platform': ^3.0.3 + pg: ^8.13.0 + dependencies: + '@pipedream/platform': 3.0.3 + pg: 8.13.0 components/nimble: specifiers: @@ -31381,6 +31386,10 @@ packages: resolution: {integrity: sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==} dev: false + /pg-connection-string/2.7.0: + resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==} + dev: false + /pg-format/1.0.4: resolution: {integrity: sha512-YyKEF78pEA6wwTAqOUaHIN/rWpfzzIuMh9KdAhc3rSLQ/7zkRFcCgYBAEGatDstLyZw4g0s9SNICmaTGnBVeyw==} engines: {node: '>=4.0'} @@ -31399,10 +31408,22 @@ packages: pg: 8.11.3 dev: false + /pg-pool/3.7.0_pg@8.13.0: + resolution: {integrity: sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==} + peerDependencies: + pg: '>=8.0' + dependencies: + pg: 8.13.0 + dev: false + /pg-protocol/1.6.0: resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==} dev: false + /pg-protocol/1.7.0: + resolution: {integrity: sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==} + dev: false + /pg-types/2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} engines: {node: '>=4'} @@ -31434,6 +31455,24 @@ packages: pg-cloudflare: 1.1.1 dev: false + /pg/8.13.0: + resolution: {integrity: sha512-34wkUTh3SxTClfoHB3pQ7bIMvw9dpFU1audQQeZG837fmHfHpr14n/AELVDoOYVDW2h5RDWU78tFjkD+erSBsw==} + engines: {node: '>= 8.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + dependencies: + pg-connection-string: 2.7.0 + pg-pool: 3.7.0_pg@8.13.0 + pg-protocol: 1.7.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.1.1 + dev: false + /pgpass/1.0.5: resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} dependencies: