Initial import of verae-zapier-app from zapier monorepo
This commit is contained in:
commit
553984c942
16 changed files with 618 additions and 0 deletions
10
NATS.md
Normal file
10
NATS.md
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
# NATS — verae-zapier-app
|
||||||
|
|
||||||
|
This Zapier Platform app **does not use NATS**.
|
||||||
|
|
||||||
|
| Direction | Address | Peer | Body |
|
||||||
|
|-----------|---------|------|------|
|
||||||
|
| IN | Zapier runtime | user Zap | create/search/trigger fields |
|
||||||
|
| OUT HTTPS | middleware `/zapier/v1/timestamp`, `/wait`, `/verify`, `/status/{id}`, `/hashes/{sha256}`, `/hashes/{sha256}?includeTree=true`, REST Hook subscribe | verae-middleware | Bearer `zmw_` |
|
||||||
|
|
||||||
|
Job completion is a REST Hook HTTP POST from webhook-deliver, not a NATS client in Zapier.
|
||||||
34
README.md
Normal file
34
README.md
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
# verae-zapier
|
||||||
|
|
||||||
|
Zapier Platform CLI app for Verae. **Phase 11** in [../TODO.md](../TODO.md).
|
||||||
|
|
||||||
|
## Role
|
||||||
|
|
||||||
|
Runs on **Zapier’s servers**. Calls only the middleware HTTPS API (`MIDDLEWARE_BASE_URL`), never NATS and never `api.veraetime.net` directly.
|
||||||
|
|
||||||
|
## Planned modules
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `authentication.js` | Custom API key auth → `GET /zapier/v1/auth/me` |
|
||||||
|
| `index.js` | App definition, beforeRequest, afterResponse error mapping |
|
||||||
|
| `creates/timestamp_and_wait.js` | Primary action |
|
||||||
|
| `creates/create_timestamp.js` | Async jobId action |
|
||||||
|
| `creates/verify_timestamp.js` | Verify certificate |
|
||||||
|
| `creates/batch_timestamp.js` | Batch create |
|
||||||
|
| `searches/job_status.js` | Lookup by jobId |
|
||||||
|
| `triggers/timestamp_completed.js` | REST Hook |
|
||||||
|
|
||||||
|
See [../docs/developer/modules/function-reference.md](../docs/developer/modules/function-reference.md) for I/O contracts.
|
||||||
|
|
||||||
|
## Env
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export MIDDLEWARE_BASE_URL=https://your-middleware.example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gate
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run gate:11 # from monorepo root, after Phase 11 implementation
|
||||||
|
```
|
||||||
9
SUMMARY.md
Normal file
9
SUMMARY.md
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
# verae-zapier-app
|
||||||
|
|
||||||
|
**Job:** Full Zapier CLI app: timestamp, wait, verify, batch, job status, hash lookup, REST Hook.
|
||||||
|
|
||||||
|
**Expects:** `zmw_` middleware key (today) or zappier key after composition.
|
||||||
|
|
||||||
|
**Sends:** HTTPS to middleware `/zapier/v1/*`.
|
||||||
|
|
||||||
|
**Does not** speak NATS.
|
||||||
38
authentication.js
Normal file
38
authentication.js
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
/**
|
||||||
|
* Custom API key auth against Verae middleware.
|
||||||
|
* @module authentication
|
||||||
|
*/
|
||||||
|
|
||||||
|
const middlewareBase = () =>
|
||||||
|
process.env.MIDDLEWARE_BASE_URL || 'http://127.0.0.1:3100';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zapier connection test — validates API key.
|
||||||
|
* @param {object} z
|
||||||
|
* @param {object} bundle
|
||||||
|
*/
|
||||||
|
const testAuth = async (z, bundle) => {
|
||||||
|
const response = await z.request({
|
||||||
|
url: `${middlewareBase()}/zapier/v1/auth/me`,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${bundle.authData.api_key}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
type: 'custom',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: 'api_key',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
label: 'API Key',
|
||||||
|
helpText:
|
||||||
|
'Your Verae API key (zmw_…) from signup or admin provisioning.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
test: testAuth,
|
||||||
|
connectionLabel: '{{tenantId}} ({{plan}})',
|
||||||
|
};
|
||||||
2
creates/add_numbers.js
Normal file
2
creates/add_numbers.js
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/** Re-export activate-now Add Numbers so this package stays self-contained. */
|
||||||
|
module.exports = require('../../verae-activate/creates/add_numbers');
|
||||||
39
creates/batch_timestamp.js
Normal file
39
creates/batch_timestamp.js
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
const base = () => process.env.MIDDLEWARE_BASE_URL || 'http://127.0.0.1:3100';
|
||||||
|
|
||||||
|
const perform = async (z, bundle) => {
|
||||||
|
const items = (bundle.inputData.items || '')
|
||||||
|
.split('\n')
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((data) => ({ data }));
|
||||||
|
|
||||||
|
const response = await z.request({
|
||||||
|
method: 'POST',
|
||||||
|
url: `${base()}/zapier/v1/timestamp/batch`,
|
||||||
|
body: { items },
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
key: 'batch_timestamp',
|
||||||
|
noun: 'Timestamp',
|
||||||
|
display: {
|
||||||
|
label: 'Create Batch Timestamps',
|
||||||
|
description: 'Submit multiple data lines for timestamping (paid plans).',
|
||||||
|
},
|
||||||
|
operation: {
|
||||||
|
inputFields: [
|
||||||
|
{
|
||||||
|
key: 'items',
|
||||||
|
label: 'Data Lines',
|
||||||
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
helpText: 'One payload per line.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
perform,
|
||||||
|
sample: { jobIds: ['id-1', 'id-2'] },
|
||||||
|
outputFields: [{ key: 'jobIds', label: 'Job IDs' }],
|
||||||
|
},
|
||||||
|
};
|
||||||
37
creates/create_timestamp.js
Normal file
37
creates/create_timestamp.js
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
const base = () => process.env.MIDDLEWARE_BASE_URL || 'http://127.0.0.1:3100';
|
||||||
|
|
||||||
|
const perform = async (z, bundle) => {
|
||||||
|
const response = await z.request({
|
||||||
|
method: 'POST',
|
||||||
|
url: `${base()}/zapier/v1/timestamp`,
|
||||||
|
body: {
|
||||||
|
data: bundle.inputData.data,
|
||||||
|
hashAlg: bundle.inputData.hashAlg || undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
key: 'create_timestamp',
|
||||||
|
noun: 'Timestamp',
|
||||||
|
display: {
|
||||||
|
label: 'Create Timestamp (Async)',
|
||||||
|
description: 'Submits data for timestamping and returns a job ID.',
|
||||||
|
},
|
||||||
|
operation: {
|
||||||
|
inputFields: [
|
||||||
|
{ key: 'data', label: 'Data', type: 'string', required: true },
|
||||||
|
{
|
||||||
|
key: 'hashAlg',
|
||||||
|
label: 'Hash Algorithm',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
default: 'SHA256',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
perform,
|
||||||
|
sample: { jobId: '550e8400-e29b-41d4-a716-446655440000' },
|
||||||
|
outputFields: [{ key: 'jobId', label: 'Job ID' }],
|
||||||
|
},
|
||||||
|
};
|
||||||
54
creates/timestamp_and_wait.js
Normal file
54
creates/timestamp_and_wait.js
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
const base = () => process.env.MIDDLEWARE_BASE_URL || 'http://127.0.0.1:3100';
|
||||||
|
|
||||||
|
const perform = async (z, bundle) => {
|
||||||
|
const response = await z.request({
|
||||||
|
method: 'POST',
|
||||||
|
url: `${base()}/zapier/v1/timestamp/wait`,
|
||||||
|
body: {
|
||||||
|
data: bundle.inputData.data,
|
||||||
|
hashAlg: bundle.inputData.hashAlg || undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
key: 'timestamp_and_wait',
|
||||||
|
noun: 'Timestamp',
|
||||||
|
display: {
|
||||||
|
label: 'Create Timestamp and Wait',
|
||||||
|
description:
|
||||||
|
'Submits data for blockchain timestamping and waits for the certificate.',
|
||||||
|
},
|
||||||
|
operation: {
|
||||||
|
inputFields: [
|
||||||
|
{
|
||||||
|
key: 'data',
|
||||||
|
label: 'Data',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
helpText: 'Content to timestamp on the blockchain.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'hashAlg',
|
||||||
|
label: 'Hash Algorithm',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
default: 'SHA256',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
perform,
|
||||||
|
sample: {
|
||||||
|
id: '550e8400-e29b-41d4-a716-446655440000',
|
||||||
|
status: 'completed',
|
||||||
|
result: 'mock-cert-example',
|
||||||
|
completedAt: '2023-01-01T12:05:00Z',
|
||||||
|
},
|
||||||
|
outputFields: [
|
||||||
|
{ key: 'id', label: 'Job ID' },
|
||||||
|
{ key: 'status', label: 'Status' },
|
||||||
|
{ key: 'result', label: 'Certificate' },
|
||||||
|
{ key: 'completedAt', label: 'Completed At', type: 'datetime' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
36
creates/verify_timestamp.js
Normal file
36
creates/verify_timestamp.js
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
const base = () => process.env.MIDDLEWARE_BASE_URL || 'http://127.0.0.1:3100';
|
||||||
|
|
||||||
|
const perform = async (z, bundle) => {
|
||||||
|
const response = await z.request({
|
||||||
|
method: 'POST',
|
||||||
|
url: `${base()}/zapier/v1/verify`,
|
||||||
|
body: { certificate: bundle.inputData.certificate },
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
key: 'verify_timestamp',
|
||||||
|
noun: 'Verification',
|
||||||
|
display: {
|
||||||
|
label: 'Verify Timestamp',
|
||||||
|
description: 'Verify a timestamp certificate.',
|
||||||
|
},
|
||||||
|
operation: {
|
||||||
|
inputFields: [
|
||||||
|
{
|
||||||
|
key: 'certificate',
|
||||||
|
label: 'Certificate',
|
||||||
|
type: 'text',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
perform,
|
||||||
|
sample: { valid: true, timestamp: '2023-01-01T12:00:00Z', blockIndex: 42 },
|
||||||
|
outputFields: [
|
||||||
|
{ key: 'valid', label: 'Valid', type: 'boolean' },
|
||||||
|
{ key: 'timestamp', label: 'Timestamp', type: 'datetime' },
|
||||||
|
{ key: 'blockIndex', label: 'Block Index', type: 'integer' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
82
index.js
Normal file
82
index.js
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
/**
|
||||||
|
* Verae Zapier Platform app definition.
|
||||||
|
* @module index
|
||||||
|
*/
|
||||||
|
|
||||||
|
const authentication = require('./authentication');
|
||||||
|
const timestampAndWait = require('./creates/timestamp_and_wait');
|
||||||
|
const createTimestamp = require('./creates/create_timestamp');
|
||||||
|
const verifyTimestamp = require('./creates/verify_timestamp');
|
||||||
|
const batchTimestamp = require('./creates/batch_timestamp');
|
||||||
|
const addNumbers = require('./creates/add_numbers');
|
||||||
|
const jobStatus = require('./searches/job_status');
|
||||||
|
const hashLookup = require('./searches/hash_lookup');
|
||||||
|
const treeLookup = require('./searches/tree_lookup');
|
||||||
|
const timestampCompleted = require('./triggers/timestamp_completed');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach middleware API key to every outbound request.
|
||||||
|
* @param {object} request
|
||||||
|
* @param {object} _z
|
||||||
|
* @param {object} bundle
|
||||||
|
*/
|
||||||
|
const addApiKey = (request, _z, bundle) => {
|
||||||
|
request.headers = request.headers || {};
|
||||||
|
request.headers.Authorization = `Bearer ${bundle.authData.api_key}`;
|
||||||
|
return request;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map middleware billing errors to Zapier errors.
|
||||||
|
* @param {object} response
|
||||||
|
* @param {object} z
|
||||||
|
*/
|
||||||
|
const mapMiddlewareErrors = (response, z) => {
|
||||||
|
if (response.status === 402) {
|
||||||
|
throw new z.errors.Error(
|
||||||
|
`${response.data?.error ?? 'Quota exceeded'}. Upgrade at ${response.data?.details?.upgradeUrl ?? response.data?.upgradeUrl ?? 'your billing portal'}.`,
|
||||||
|
'QuotaExceeded',
|
||||||
|
402,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.status === 403 && response.data?.code === 'PLAN_UPGRADE_REQUIRED') {
|
||||||
|
throw new z.errors.Error(
|
||||||
|
response.data?.error ?? 'This action requires a paid plan.',
|
||||||
|
'PlanUpgradeRequired',
|
||||||
|
403,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
|
let platformVersion = '15.19.0';
|
||||||
|
try {
|
||||||
|
platformVersion = require('zapier-platform-core').version;
|
||||||
|
} catch {
|
||||||
|
// optional for unit tests without full install
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
version: require('./package.json').version,
|
||||||
|
platformVersion,
|
||||||
|
authentication,
|
||||||
|
beforeRequest: [addApiKey],
|
||||||
|
afterResponse: [mapMiddlewareErrors],
|
||||||
|
triggers: {
|
||||||
|
[timestampCompleted.key]: timestampCompleted,
|
||||||
|
},
|
||||||
|
creates: {
|
||||||
|
[addNumbers.key]: addNumbers,
|
||||||
|
[timestampAndWait.key]: timestampAndWait,
|
||||||
|
[createTimestamp.key]: createTimestamp,
|
||||||
|
[verifyTimestamp.key]: verifyTimestamp,
|
||||||
|
[batchTimestamp.key]: batchTimestamp,
|
||||||
|
},
|
||||||
|
searches: {
|
||||||
|
[jobStatus.key]: jobStatus,
|
||||||
|
[hashLookup.key]: hashLookup,
|
||||||
|
[treeLookup.key]: treeLookup,
|
||||||
|
},
|
||||||
|
};
|
||||||
17
package.json
Normal file
17
package.json
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"name": "verae-zapier",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Zapier CLI app for Verae Timestamping via middleware",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "node --test test/**/*.test.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18",
|
||||||
|
"npm": ">=5.6.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"zapier-platform-core": "15.19.0"
|
||||||
|
},
|
||||||
|
"private": true
|
||||||
|
}
|
||||||
34
searches/hash_lookup.js
Normal file
34
searches/hash_lookup.js
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
const base = () => process.env.MIDDLEWARE_BASE_URL || 'http://127.0.0.1:3100';
|
||||||
|
|
||||||
|
const perform = async (z, bundle) => {
|
||||||
|
try {
|
||||||
|
const response = await z.request({
|
||||||
|
method: 'GET',
|
||||||
|
url: `${base()}/zapier/v1/hashes/${encodeURIComponent(bundle.inputData.sha256)}`,
|
||||||
|
});
|
||||||
|
return [response.data];
|
||||||
|
} catch (err) {
|
||||||
|
if (err.status === 404) return [];
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
key: 'hash_lookup',
|
||||||
|
noun: 'Timestamp',
|
||||||
|
display: {
|
||||||
|
label: 'Find Timestamp by SHA256',
|
||||||
|
description: 'Looks up an existing timestamp for a SHA256 hex digest (mock/middleware).',
|
||||||
|
},
|
||||||
|
operation: {
|
||||||
|
inputFields: [
|
||||||
|
{ key: 'sha256', label: 'SHA256', type: 'string', required: true },
|
||||||
|
],
|
||||||
|
perform,
|
||||||
|
sample: {
|
||||||
|
sha256: '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824',
|
||||||
|
exists: true,
|
||||||
|
jobId: '550e8400-e29b-41d4-a716-446655440000',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
33
searches/job_status.js
Normal file
33
searches/job_status.js
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
const base = () => process.env.MIDDLEWARE_BASE_URL || 'http://127.0.0.1:3100';
|
||||||
|
|
||||||
|
const perform = async (z, bundle) => {
|
||||||
|
const response = await z.request({
|
||||||
|
url: `${base()}/zapier/v1/status/${encodeURIComponent(bundle.inputData.jobId)}`,
|
||||||
|
});
|
||||||
|
return [response.data];
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
key: 'job_status',
|
||||||
|
noun: 'Job',
|
||||||
|
display: {
|
||||||
|
label: 'Find Job Status',
|
||||||
|
description: 'Look up a timestamp job by ID.',
|
||||||
|
},
|
||||||
|
operation: {
|
||||||
|
inputFields: [
|
||||||
|
{ key: 'jobId', label: 'Job ID', type: 'string', required: true },
|
||||||
|
],
|
||||||
|
perform,
|
||||||
|
sample: {
|
||||||
|
id: '550e8400-e29b-41d4-a716-446655440000',
|
||||||
|
status: 'completed',
|
||||||
|
result: 'mock-cert',
|
||||||
|
},
|
||||||
|
outputFields: [
|
||||||
|
{ key: 'id', label: 'Job ID' },
|
||||||
|
{ key: 'status', label: 'Status' },
|
||||||
|
{ key: 'result', label: 'Result' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
45
searches/tree_lookup.js
Normal file
45
searches/tree_lookup.js
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
const base = () => process.env.MIDDLEWARE_BASE_URL || 'http://127.0.0.1:3100';
|
||||||
|
|
||||||
|
const perform = async (z, bundle) => {
|
||||||
|
try {
|
||||||
|
const sha = encodeURIComponent(bundle.inputData.sha256);
|
||||||
|
const response = await z.request({
|
||||||
|
method: 'GET',
|
||||||
|
url: `${base()}/zapier/v1/hashes/${sha}?includeAttached=true&includeTree=true`,
|
||||||
|
});
|
||||||
|
return [response.data];
|
||||||
|
} catch (err) {
|
||||||
|
if (err.status === 404) return [];
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
key: 'tree_lookup',
|
||||||
|
noun: 'Timestamp',
|
||||||
|
display: {
|
||||||
|
label: 'Find Hash (tree nodes + central chain)',
|
||||||
|
description:
|
||||||
|
'Looks up a SHA256 on the main Verae chain, then queries external tree-node archives for leaves that were only sealed as a bulk Merkle summary.',
|
||||||
|
},
|
||||||
|
operation: {
|
||||||
|
inputFields: [
|
||||||
|
{
|
||||||
|
key: 'sha256',
|
||||||
|
label: 'SHA256',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
helpText:
|
||||||
|
'64-char hex. If this hash was part of a batch, it may not be itemized on the main chain — this search still finds the Merkle proof on tree nodes.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
perform,
|
||||||
|
sample: {
|
||||||
|
sha256: '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824',
|
||||||
|
exists: true,
|
||||||
|
itemizedOnMainChain: false,
|
||||||
|
merkleRoot: 'ab'.repeat(32),
|
||||||
|
proofOk: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
95
test/app.test.js
Normal file
95
test/app.test.js
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
/**
|
||||||
|
* GATE 11 — Zapier package shape tests (no live Zapier CLI required)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { describe, it } = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
|
||||||
|
const App = require('../index');
|
||||||
|
const authentication = require('../authentication');
|
||||||
|
const timestampAndWait = require('../creates/timestamp_and_wait');
|
||||||
|
const createTimestamp = require('../creates/create_timestamp');
|
||||||
|
const verifyTimestamp = require('../creates/verify_timestamp');
|
||||||
|
const batchTimestamp = require('../creates/batch_timestamp');
|
||||||
|
const jobStatus = require('../searches/job_status');
|
||||||
|
const timestampCompleted = require('../triggers/timestamp_completed');
|
||||||
|
|
||||||
|
describe('verae-zapier app definition', () => {
|
||||||
|
it('exports authentication with api_key field', () => {
|
||||||
|
assert.equal(authentication.type, 'custom');
|
||||||
|
assert.ok(authentication.fields.some((f) => f.key === 'api_key'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('wires creates, searches, triggers', () => {
|
||||||
|
assert.ok(App.creates.add_numbers);
|
||||||
|
assert.ok(App.creates.timestamp_and_wait);
|
||||||
|
assert.ok(App.creates.create_timestamp);
|
||||||
|
assert.ok(App.creates.verify_timestamp);
|
||||||
|
assert.ok(App.creates.batch_timestamp);
|
||||||
|
assert.ok(App.searches.job_status);
|
||||||
|
assert.ok(App.searches.hash_lookup);
|
||||||
|
assert.ok(App.searches.tree_lookup);
|
||||||
|
assert.ok(App.triggers.timestamp_completed);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('create actions target middleware paths', () => {
|
||||||
|
process.env.MIDDLEWARE_BASE_URL = 'https://mw.example.com';
|
||||||
|
const urls = [];
|
||||||
|
const z = {
|
||||||
|
request: async (opts) => {
|
||||||
|
urls.push(opts.url);
|
||||||
|
return { data: { ok: true } };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const bundle = {
|
||||||
|
authData: { api_key: 'zmw_test' },
|
||||||
|
inputData: { data: 'x', certificate: 'c', items: 'a\nb', jobId: 'j1' },
|
||||||
|
};
|
||||||
|
|
||||||
|
return Promise.all([
|
||||||
|
timestampAndWait.operation.perform(z, bundle),
|
||||||
|
createTimestamp.operation.perform(z, bundle),
|
||||||
|
verifyTimestamp.operation.perform(z, bundle),
|
||||||
|
batchTimestamp.operation.perform(z, bundle),
|
||||||
|
jobStatus.operation.perform(z, bundle),
|
||||||
|
]).then(() => {
|
||||||
|
assert.ok(urls.some((u) => u.endsWith('/zapier/v1/timestamp/wait')));
|
||||||
|
assert.ok(urls.some((u) => u.endsWith('/zapier/v1/timestamp')));
|
||||||
|
assert.ok(urls.some((u) => u.endsWith('/zapier/v1/verify')));
|
||||||
|
assert.ok(urls.some((u) => u.endsWith('/zapier/v1/timestamp/batch')));
|
||||||
|
assert.ok(urls.some((u) => u.includes('/zapier/v1/status/')));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('trigger subscribe/unsubscribe shapes', async () => {
|
||||||
|
process.env.MIDDLEWARE_BASE_URL = 'https://mw.example.com';
|
||||||
|
const calls = [];
|
||||||
|
const z = {
|
||||||
|
request: async (opts) => {
|
||||||
|
calls.push(opts);
|
||||||
|
return { data: { id: 'hook-1' } };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const id = await timestampCompleted.operation.performSubscribe(z, {
|
||||||
|
targetUrl: 'https://hooks.zapier.com/x',
|
||||||
|
authData: { api_key: 'k' },
|
||||||
|
});
|
||||||
|
assert.equal(id, 'hook-1');
|
||||||
|
assert.equal(calls[0].method, 'POST');
|
||||||
|
assert.match(calls[0].url, /webhooks\/subscribe$/);
|
||||||
|
assert.equal(calls[0].body.event, 'timestamp.completed');
|
||||||
|
|
||||||
|
await timestampCompleted.operation.performUnsubscribe(z, {
|
||||||
|
subscribeData: 'hook-1',
|
||||||
|
authData: { api_key: 'k' },
|
||||||
|
});
|
||||||
|
assert.equal(calls[1].method, 'DELETE');
|
||||||
|
assert.match(calls[1].url, /webhooks\/unsubscribe$/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('beforeRequest adds Authorization bearer', () => {
|
||||||
|
const req = App.beforeRequest[0]({}, null, { authData: { api_key: 'zmw_abc' } });
|
||||||
|
assert.equal(req.headers.Authorization, 'Bearer zmw_abc');
|
||||||
|
});
|
||||||
|
});
|
||||||
53
triggers/timestamp_completed.js
Normal file
53
triggers/timestamp_completed.js
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
const base = () => process.env.MIDDLEWARE_BASE_URL || 'http://127.0.0.1:3100';
|
||||||
|
|
||||||
|
const subscribeHook = async (z, bundle) => {
|
||||||
|
const response = await z.request({
|
||||||
|
method: 'POST',
|
||||||
|
url: `${base()}/zapier/v1/webhooks/subscribe`,
|
||||||
|
body: {
|
||||||
|
targetUrl: bundle.targetUrl,
|
||||||
|
event: 'timestamp.completed',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return response.data.id;
|
||||||
|
};
|
||||||
|
|
||||||
|
const unsubscribeHook = async (z, bundle) => {
|
||||||
|
await z.request({
|
||||||
|
method: 'DELETE',
|
||||||
|
url: `${base()}/zapier/v1/webhooks/unsubscribe`,
|
||||||
|
body: {
|
||||||
|
hookId: bundle.subscribeData,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
const perform = async (z, bundle) => [bundle.cleanedRequest];
|
||||||
|
|
||||||
|
const performList = async () => [];
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
key: 'timestamp_completed',
|
||||||
|
noun: 'Timestamp',
|
||||||
|
display: {
|
||||||
|
label: 'Timestamp Completed',
|
||||||
|
description: 'Triggers when a blockchain timestamp job completes.',
|
||||||
|
},
|
||||||
|
operation: {
|
||||||
|
type: 'hook',
|
||||||
|
perform,
|
||||||
|
performList,
|
||||||
|
performSubscribe: subscribeHook,
|
||||||
|
performUnsubscribe: unsubscribeHook,
|
||||||
|
sample: {
|
||||||
|
event: 'timestamp.completed',
|
||||||
|
jobId: '550e8400-e29b-41d4-a716-446655440000',
|
||||||
|
status: { id: '550e8400-e29b-41d4-a716-446655440000', status: 'completed' },
|
||||||
|
},
|
||||||
|
outputFields: [
|
||||||
|
{ key: 'event', label: 'Event' },
|
||||||
|
{ key: 'jobId', label: 'Job ID' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
Loading…
Add table
Add a link
Reference in a new issue