Keep-going: tree shares, health flags, CI, compose, timestamp Zapier action
Directory-tree encrypted share mock; /health reports mockVerae/nats; middleware OpenAPI extended; zappier receipt by jobId; activate Create Timestamp (local mock or hosted); GitHub Actions test:offline; compose builds zappier image; seed-demo.mjs. No Zapier login or live Verae required.
This commit is contained in:
parent
645a24909a
commit
5925e76c72
39 changed files with 514 additions and 33 deletions
20
.github/workflows/offline.yml
vendored
Normal file
20
.github/workflows/offline.yml
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
name: offline
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '22'
|
||||||
|
- run: npm --prefix packages/verae-zapier-middleware install
|
||||||
|
- run: npm --prefix packages/zappier install
|
||||||
|
- run: npm --prefix packages/verae-activate install
|
||||||
|
- run: npm run test:offline
|
||||||
|
env:
|
||||||
|
MOCK_VERAE: 'true'
|
||||||
|
NATS_ENABLED: 'false'
|
||||||
|
|
@ -103,7 +103,7 @@ If `register` says the name is taken, use `"Verae Time Dev"` or `"VeraeTime"`.
|
||||||
- **API Key** — leave **blank**.
|
- **API Key** — leave **blank**.
|
||||||
- **API base URL** — leave **blank**.
|
- **API base URL** — leave **blank**.
|
||||||
- Test connection → should succeed (*Verae Time (Add Numbers)* / local mode).
|
- Test connection → should succeed (*Verae Time (Add Numbers)* / local mode).
|
||||||
5. Choose action **Add Numbers** (or **Echo Text**).
|
5. Choose action **Add Numbers**, **Echo Text**, **SHA256 Hash Text**, or **Create Timestamp** (local mock if API fields blank).
|
||||||
6. Set **Number 1** = `2`, **Number 2** = `3`.
|
6. Set **Number 1** = `2`, **Number 2** = `3`.
|
||||||
7. **Test step**. You want:
|
7. **Test step**. You want:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ These cannot finish in this session:
|
||||||
1. **`zapier-platform login` + `register` + `push`** — browser OAuth to your Zapier account. Follow [docs/04-activate/SETUP-ZAPIER-DEVELOPER.md](04-activate/SETUP-ZAPIER-DEVELOPER.md). After push, commit `.zapierapprc`.
|
1. **`zapier-platform login` + `register` + `push`** — browser OAuth to your Zapier account. Follow [docs/04-activate/SETUP-ZAPIER-DEVELOPER.md](04-activate/SETUP-ZAPIER-DEVELOPER.md). After push, commit `.zapierapprc`.
|
||||||
2. **Live `api.veraetime.net`** — hash lookup, metadata, receipts, LTS, share are **not** in the public OpenAPI. Mocks exist; live client refuses unknown paths.
|
2. **Live `api.veraetime.net`** — hash lookup, metadata, receipts, LTS, share are **not** in the public OpenAPI. Mocks exist; live client refuses unknown paths.
|
||||||
3. **PDF receipts** — JSON extra-seal works; PDF is generated locally (`?format=pdf`). Legal copy/seal artwork still TBD.
|
3. **PDF receipts** — JSON extra-seal works; PDF is generated locally (`?format=pdf`). Legal copy/seal artwork still TBD.
|
||||||
4. **Encrypted LTS + share** — mock AES-256-GCM blobs + share tokens exist (`/zapier/v1/objects`, `/zapier/v1/shares/:token`). Not live Verae/Peergos.
|
4. **Encrypted LTS + share** — file **and directory tree** mocks: `/zapier/v1/objects`, `/objects/trees`, `/shares/:token`. Not live Verae/Peergos.
|
||||||
5. **NS1 NATS public bind** — left loopback-only; `scripts/nats-tunnel.sh` maps `14222`.
|
5. **NS1 NATS public bind** — left loopback-only; `scripts/nats-tunnel.sh` maps `14222`.
|
||||||
|
|
||||||
Everything else for morning Add Numbers is ready.
|
Everything else for morning Add Numbers is ready.
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,63 @@ paths:
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Unsubscribed
|
description: Unsubscribed
|
||||||
|
/zapier/v1/hashes/{sha256}:
|
||||||
|
get:
|
||||||
|
summary: Lookup timestamp by SHA256 (mock)
|
||||||
|
operationId: lookupHash
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
parameters:
|
||||||
|
- name: sha256
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema: { type: string }
|
||||||
|
responses:
|
||||||
|
'200': { description: Found }
|
||||||
|
'404': { description: Missing }
|
||||||
|
/zapier/v1/receipts/{jobId}:
|
||||||
|
get:
|
||||||
|
summary: Certified retrieval receipt (json or pdf)
|
||||||
|
operationId: getReceipt
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
parameters:
|
||||||
|
- name: jobId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema: { type: string }
|
||||||
|
- name: format
|
||||||
|
in: query
|
||||||
|
schema: { type: string, enum: [json, pdf] }
|
||||||
|
responses:
|
||||||
|
'200': { description: Receipt }
|
||||||
|
/zapier/v1/objects:
|
||||||
|
post:
|
||||||
|
summary: Store encrypted blob and timestamp
|
||||||
|
operationId: putObject
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
responses:
|
||||||
|
'201': { description: Stored }
|
||||||
|
/zapier/v1/objects/trees:
|
||||||
|
post:
|
||||||
|
summary: Store encrypted directory tree
|
||||||
|
operationId: putTree
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
responses:
|
||||||
|
'201': { description: Stored }
|
||||||
|
/zapier/v1/shares/{token}:
|
||||||
|
get:
|
||||||
|
summary: Redeem a share token
|
||||||
|
operationId: redeemShare
|
||||||
|
parameters:
|
||||||
|
- name: token
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema: { type: string }
|
||||||
|
responses:
|
||||||
|
'200': { description: Ciphertext + dek (mock) }
|
||||||
components:
|
components:
|
||||||
securitySchemes:
|
securitySchemes:
|
||||||
BearerAuth:
|
BearerAuth:
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ One MD file per source module.
|
||||||
- [verae-activate/creates/add_numbers](verae-activate/creates/add_numbers.md)
|
- [verae-activate/creates/add_numbers](verae-activate/creates/add_numbers.md)
|
||||||
- [verae-activate/creates/echo](verae-activate/creates/echo.md)
|
- [verae-activate/creates/echo](verae-activate/creates/echo.md)
|
||||||
- [verae-activate/creates/sha256](verae-activate/creates/sha256.md)
|
- [verae-activate/creates/sha256](verae-activate/creates/sha256.md)
|
||||||
|
- [verae-activate/creates/timestamp](verae-activate/creates/timestamp.md)
|
||||||
- [verae-activate/index](verae-activate/index.md)
|
- [verae-activate/index](verae-activate/index.md)
|
||||||
- [verae-zapier/authentication](verae-zapier/authentication.md)
|
- [verae-zapier/authentication](verae-zapier/authentication.md)
|
||||||
- [verae-zapier/creates/add_numbers](verae-zapier/creates/add_numbers.md)
|
- [verae-zapier/creates/add_numbers](verae-zapier/creates/add_numbers.md)
|
||||||
|
|
|
||||||
40
docs/modules/verae-activate/creates/timestamp.md
Normal file
40
docs/modules/verae-activate/creates/timestamp.md
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
# `verae-activate/creates/timestamp`
|
||||||
|
|
||||||
|
**Package:** `verae-activate`
|
||||||
|
**Source:** `packages/verae-activate/creates/timestamp.js`
|
||||||
|
**Lines:** 55
|
||||||
|
|
||||||
|
## What this module is
|
||||||
|
|
||||||
|
Implementation module in `verae-activate`. The tables below are extracted from the source (signatures + JSDoc).
|
||||||
|
|
||||||
|
## Exports
|
||||||
|
|
||||||
|
`key`, `noun`, `display`, `label`, `description`
|
||||||
|
|
||||||
|
## Types / interfaces / classes
|
||||||
|
|
||||||
|
_None extracted._
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|
||||||
|
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||||
|
|------|------------|---------------------|---------|-----------------------------|
|
||||||
|
| `perform` | `z, bundle` | — | `unknown` | see Call graph |
|
||||||
|
|
||||||
|
## What it imports / requires
|
||||||
|
|
||||||
|
- `node:crypto`
|
||||||
|
|
||||||
|
## Call graph (identifiers invoked)
|
||||||
|
|
||||||
|
`async`, `replace`, `request`, `randomUUID`
|
||||||
|
|
||||||
|
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||||
|
|
||||||
|
## Return values (how to read this)
|
||||||
|
|
||||||
|
- HTTP route handlers return Express `res.json(...)` bodies (see route docs).
|
||||||
|
- Zapier `perform` functions return a **single object** (creates) or an **array** (triggers/searches).
|
||||||
|
- Pricing functions return integer **cents** on `Quote.totalCents`.
|
||||||
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
**Package:** `verae-activate`
|
**Package:** `verae-activate`
|
||||||
**Source:** `packages/verae-activate/index.js`
|
**Source:** `packages/verae-activate/index.js`
|
||||||
**Lines:** 25
|
**Lines:** 27
|
||||||
|
|
||||||
## What this module is
|
## What this module is
|
||||||
|
|
||||||
|
|
@ -10,7 +10,7 @@ Implementation module in `verae-activate`. The tables below are extracted from t
|
||||||
|
|
||||||
## Exports
|
## Exports
|
||||||
|
|
||||||
`version`, `platformVersion`, `authentication`, `creates`, `addNumbers`, `echo`, `sha256Hash`
|
`version`, `platformVersion`, `authentication`, `creates`, `addNumbers`, `echo`, `sha256Hash`, `createTimestamp`
|
||||||
|
|
||||||
## Types / interfaces / classes
|
## Types / interfaces / classes
|
||||||
|
|
||||||
|
|
@ -26,6 +26,7 @@ _No top-level functions extracted._
|
||||||
- `./creates/add_numbers`
|
- `./creates/add_numbers`
|
||||||
- `./creates/echo`
|
- `./creates/echo`
|
||||||
- `./creates/sha256`
|
- `./creates/sha256`
|
||||||
|
- `./creates/timestamp`
|
||||||
- `./package.json`
|
- `./package.json`
|
||||||
- `zapier-platform-core`
|
- `zapier-platform-core`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
**Package:** `verae-zapier-middleware`
|
**Package:** `verae-zapier-middleware`
|
||||||
**Source:** `packages/verae-zapier-middleware/src/app.js`
|
**Source:** `packages/verae-zapier-middleware/src/app.js`
|
||||||
**Lines:** 51
|
**Lines:** 56
|
||||||
|
|
||||||
## What this module is
|
## What this module is
|
||||||
|
|
||||||
|
|
@ -30,10 +30,12 @@ _None extracted._
|
||||||
- `./debug/trace.js`
|
- `./debug/trace.js`
|
||||||
- `./routes/index.js`
|
- `./routes/index.js`
|
||||||
- `./store/db.js`
|
- `./store/db.js`
|
||||||
|
- `./config.js`
|
||||||
|
- `./nats/connection.js`
|
||||||
|
|
||||||
## Call graph (identifiers invoked)
|
## Call graph (identifiers invoked)
|
||||||
|
|
||||||
`createDebugger`, `app`, `import`, `createApp`, `loadStore`, `express`, `disable`, `use`, `json`, `get`, `sendError`, `info`
|
`createDebugger`, `app`, `import`, `createApp`, `loadStore`, `express`, `disable`, `use`, `json`, `get`, `isNatsConnected`, `sendError`, `info`
|
||||||
|
|
||||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
**Package:** `verae-zapier-middleware`
|
**Package:** `verae-zapier-middleware`
|
||||||
**Source:** `packages/verae-zapier-middleware/src/routes/objectRoutes.js`
|
**Source:** `packages/verae-zapier-middleware/src/routes/objectRoutes.js`
|
||||||
**Lines:** 91
|
**Lines:** 126
|
||||||
|
|
||||||
## What this module is
|
## What this module is
|
||||||
|
|
||||||
|
|
@ -28,6 +28,8 @@ _No top-level functions extracted._
|
||||||
| `asyncHandler` | `async (req, res` |
|
| `asyncHandler` | `async (req, res` |
|
||||||
| `asyncHandler` | `async (req, res` |
|
| `asyncHandler` | `async (req, res` |
|
||||||
| `asyncHandler` | `async (req, res` |
|
| `asyncHandler` | `async (req, res` |
|
||||||
|
| `asyncHandler` | `async (req, res` |
|
||||||
|
| `asyncHandler` | `async (req, res` |
|
||||||
|
|
||||||
## What it imports / requires
|
## What it imports / requires
|
||||||
|
|
||||||
|
|
@ -38,7 +40,7 @@ _No top-level functions extracted._
|
||||||
|
|
||||||
## Call graph (identifiers invoked)
|
## Call graph (identifiers invoked)
|
||||||
|
|
||||||
`post`, `asyncHandler`, `async`, `from`, `createTimestamp`, `toString`, `slice`, `putBlob`, `status`, `json`, `get`, `readBlobPlaintext`, `createShare`, `redeemShare`
|
`post`, `asyncHandler`, `async`, `from`, `createTimestamp`, `toString`, `slice`, `putBlob`, `status`, `json`, `get`, `readBlobPlaintext`, `isArray`, `putTree`, `map`, `createTreeShare`, `createShare`, `redeemShare`
|
||||||
|
|
||||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
**Package:** `verae-zapier-middleware`
|
**Package:** `verae-zapier-middleware`
|
||||||
**Source:** `packages/verae-zapier-middleware/src/store/blobs.js`
|
**Source:** `packages/verae-zapier-middleware/src/store/blobs.js`
|
||||||
**Lines:** 137
|
**Lines:** 209
|
||||||
|
|
||||||
## What this module is
|
## What this module is
|
||||||
|
|
||||||
|
|
@ -10,7 +10,7 @@ Implementation module in `verae-zapier-middleware`. The tables below are extract
|
||||||
|
|
||||||
## Exports
|
## Exports
|
||||||
|
|
||||||
`decryptBlob`, `putBlob`, `getBlobRecord`, `readBlobPlaintext`, `createShare`, `redeemShare`
|
`decryptBlob`, `putBlob`, `getBlobRecord`, `readBlobPlaintext`, `createShare`, `redeemShare`, `putTree`, `getTree`, `createTreeShare`
|
||||||
|
|
||||||
## Types / interfaces / classes
|
## Types / interfaces / classes
|
||||||
|
|
||||||
|
|
@ -28,6 +28,9 @@ _None extracted._
|
||||||
| `readBlobPlaintext` | `id, tenantId` | id: `string`, tenantId: `string` | `unknown` | see Call graph |
|
| `readBlobPlaintext` | `id, tenantId` | id: `string`, tenantId: `string` | `unknown` | see Call graph |
|
||||||
| `createShare` | `{ blobId, fromTenantId, label }` | — | `unknown` | see Call graph |
|
| `createShare` | `{ blobId, fromTenantId, label }` | — | `unknown` | see Call graph |
|
||||||
| `redeemShare` | `token` | token: `string` | `unknown` | see Call graph |
|
| `redeemShare` | `token` | token: `string` | `unknown` | see Call graph |
|
||||||
|
| `putTree` | `{ tenantId, prefix, files }` | — | `unknown` | see Call graph |
|
||||||
|
| `getTree` | `id` | — | `unknown` | see Call graph |
|
||||||
|
| `createTreeShare` | `{ treeId, fromTenantId, label }` | — | `unknown` | see Call graph |
|
||||||
|
|
||||||
## Methods (class / object)
|
## Methods (class / object)
|
||||||
|
|
||||||
|
|
@ -35,6 +38,8 @@ _None extracted._
|
||||||
|------|------------|
|
|------|------------|
|
||||||
| `persist` | `(none)` |
|
| `persist` | `(none)` |
|
||||||
| `persist` | `(none)` |
|
| `persist` | `(none)` |
|
||||||
|
| `persist` | `(none)` |
|
||||||
|
| `persist` | `(none)` |
|
||||||
|
|
||||||
## What it imports / requires
|
## What it imports / requires
|
||||||
|
|
||||||
|
|
@ -43,7 +48,7 @@ _None extracted._
|
||||||
|
|
||||||
## Call graph (identifiers invoked)
|
## Call graph (identifiers invoked)
|
||||||
|
|
||||||
`ensure`, `getStore`, `encrypt`, `randomBytes`, `createCipheriv`, `concat`, `update`, `final`, `getAuthTag`, `toString`, `decryptBlob`, `createDecipheriv`, `from`, `setAuthTag`, `putBlob`, `randomUUID`, `toISOString`, `persist`, `getBlobRecord`, `readBlobPlaintext`, `createShare`, `redeemShare`
|
`ensure`, `getStore`, `encrypt`, `randomBytes`, `createCipheriv`, `concat`, `update`, `final`, `getAuthTag`, `toString`, `decryptBlob`, `createDecipheriv`, `from`, `setAuthTag`, `putBlob`, `randomUUID`, `toISOString`, `persist`, `getBlobRecord`, `readBlobPlaintext`, `createShare`, `redeemShare`, `map`, `putTree`, `split`, `pop`, `push`, `getTree`, `createTreeShare`
|
||||||
|
|
||||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
**Package:** `verae-zapier-middleware`
|
**Package:** `verae-zapier-middleware`
|
||||||
**Source:** `packages/verae-zapier-middleware/src/store/db.js`
|
**Source:** `packages/verae-zapier-middleware/src/store/db.js`
|
||||||
**Lines:** 117
|
**Lines:** 118
|
||||||
|
|
||||||
## What this module is
|
## What this module is
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
**Package:** `zappier`
|
**Package:** `zappier`
|
||||||
**Source:** `packages/zappier/src/app.ts`
|
**Source:** `packages/zappier/src/app.ts`
|
||||||
**Lines:** 237
|
**Lines:** 257
|
||||||
|
|
||||||
## What this module is
|
## What this module is
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
**Package:** `zappier`
|
**Package:** `zappier`
|
||||||
**Source:** `packages/zappier/src/pricing.ts`
|
**Source:** `packages/zappier/src/pricing.ts`
|
||||||
**Lines:** 173
|
**Lines:** 174
|
||||||
|
|
||||||
## What this module is
|
## What this module is
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ Verae × Zapier module reference
|
||||||
modules/verae-activate/creates/add_numbers
|
modules/verae-activate/creates/add_numbers
|
||||||
modules/verae-activate/creates/echo
|
modules/verae-activate/creates/echo
|
||||||
modules/verae-activate/creates/sha256
|
modules/verae-activate/creates/sha256
|
||||||
|
modules/verae-activate/creates/timestamp
|
||||||
modules/verae-activate/index
|
modules/verae-activate/index
|
||||||
modules/verae-zapier/authentication
|
modules/verae-zapier/authentication
|
||||||
modules/verae-zapier/creates/add_numbers
|
modules/verae-zapier/creates/add_numbers
|
||||||
|
|
|
||||||
40
docs/sphinx/modules/verae-activate/creates/timestamp.md
Normal file
40
docs/sphinx/modules/verae-activate/creates/timestamp.md
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
# `verae-activate/creates/timestamp`
|
||||||
|
|
||||||
|
**Package:** `verae-activate`
|
||||||
|
**Source:** `packages/verae-activate/creates/timestamp.js`
|
||||||
|
**Lines:** 55
|
||||||
|
|
||||||
|
## What this module is
|
||||||
|
|
||||||
|
Implementation module in `verae-activate`. The tables below are extracted from the source (signatures + JSDoc).
|
||||||
|
|
||||||
|
## Exports
|
||||||
|
|
||||||
|
`key`, `noun`, `display`, `label`, `description`
|
||||||
|
|
||||||
|
## Types / interfaces / classes
|
||||||
|
|
||||||
|
_None extracted._
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|
||||||
|
| Name | Parameters | Param types (JSDoc) | Returns | Calls (same file / helpers) |
|
||||||
|
|------|------------|---------------------|---------|-----------------------------|
|
||||||
|
| `perform` | `z, bundle` | — | `unknown` | see Call graph |
|
||||||
|
|
||||||
|
## What it imports / requires
|
||||||
|
|
||||||
|
- `node:crypto`
|
||||||
|
|
||||||
|
## Call graph (identifiers invoked)
|
||||||
|
|
||||||
|
`async`, `replace`, `request`, `randomUUID`
|
||||||
|
|
||||||
|
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||||
|
|
||||||
|
## Return values (how to read this)
|
||||||
|
|
||||||
|
- HTTP route handlers return Express `res.json(...)` bodies (see route docs).
|
||||||
|
- Zapier `perform` functions return a **single object** (creates) or an **array** (triggers/searches).
|
||||||
|
- Pricing functions return integer **cents** on `Quote.totalCents`.
|
||||||
|
|
||||||
7
docs/sphinx/modules/verae-activate/creates/timestamp.rst
Normal file
7
docs/sphinx/modules/verae-activate/creates/timestamp.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
verae-activate.creates.timestamp
|
||||||
|
================================
|
||||||
|
|
||||||
|
Generated API sheet for ``verae-activate/creates/timestamp``.
|
||||||
|
|
||||||
|
.. include:: timestamp.md
|
||||||
|
:literal:
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
**Package:** `verae-activate`
|
**Package:** `verae-activate`
|
||||||
**Source:** `packages/verae-activate/index.js`
|
**Source:** `packages/verae-activate/index.js`
|
||||||
**Lines:** 25
|
**Lines:** 27
|
||||||
|
|
||||||
## What this module is
|
## What this module is
|
||||||
|
|
||||||
|
|
@ -10,7 +10,7 @@ Implementation module in `verae-activate`. The tables below are extracted from t
|
||||||
|
|
||||||
## Exports
|
## Exports
|
||||||
|
|
||||||
`version`, `platformVersion`, `authentication`, `creates`, `addNumbers`, `echo`, `sha256Hash`
|
`version`, `platformVersion`, `authentication`, `creates`, `addNumbers`, `echo`, `sha256Hash`, `createTimestamp`
|
||||||
|
|
||||||
## Types / interfaces / classes
|
## Types / interfaces / classes
|
||||||
|
|
||||||
|
|
@ -26,6 +26,7 @@ _No top-level functions extracted._
|
||||||
- `./creates/add_numbers`
|
- `./creates/add_numbers`
|
||||||
- `./creates/echo`
|
- `./creates/echo`
|
||||||
- `./creates/sha256`
|
- `./creates/sha256`
|
||||||
|
- `./creates/timestamp`
|
||||||
- `./package.json`
|
- `./package.json`
|
||||||
- `zapier-platform-core`
|
- `zapier-platform-core`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
**Package:** `verae-zapier-middleware`
|
**Package:** `verae-zapier-middleware`
|
||||||
**Source:** `packages/verae-zapier-middleware/src/app.js`
|
**Source:** `packages/verae-zapier-middleware/src/app.js`
|
||||||
**Lines:** 51
|
**Lines:** 56
|
||||||
|
|
||||||
## What this module is
|
## What this module is
|
||||||
|
|
||||||
|
|
@ -30,10 +30,12 @@ _None extracted._
|
||||||
- `./debug/trace.js`
|
- `./debug/trace.js`
|
||||||
- `./routes/index.js`
|
- `./routes/index.js`
|
||||||
- `./store/db.js`
|
- `./store/db.js`
|
||||||
|
- `./config.js`
|
||||||
|
- `./nats/connection.js`
|
||||||
|
|
||||||
## Call graph (identifiers invoked)
|
## Call graph (identifiers invoked)
|
||||||
|
|
||||||
`createDebugger`, `app`, `import`, `createApp`, `loadStore`, `express`, `disable`, `use`, `json`, `get`, `sendError`, `info`
|
`createDebugger`, `app`, `import`, `createApp`, `loadStore`, `express`, `disable`, `use`, `json`, `get`, `isNatsConnected`, `sendError`, `info`
|
||||||
|
|
||||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
**Package:** `verae-zapier-middleware`
|
**Package:** `verae-zapier-middleware`
|
||||||
**Source:** `packages/verae-zapier-middleware/src/routes/objectRoutes.js`
|
**Source:** `packages/verae-zapier-middleware/src/routes/objectRoutes.js`
|
||||||
**Lines:** 91
|
**Lines:** 126
|
||||||
|
|
||||||
## What this module is
|
## What this module is
|
||||||
|
|
||||||
|
|
@ -28,6 +28,8 @@ _No top-level functions extracted._
|
||||||
| `asyncHandler` | `async (req, res` |
|
| `asyncHandler` | `async (req, res` |
|
||||||
| `asyncHandler` | `async (req, res` |
|
| `asyncHandler` | `async (req, res` |
|
||||||
| `asyncHandler` | `async (req, res` |
|
| `asyncHandler` | `async (req, res` |
|
||||||
|
| `asyncHandler` | `async (req, res` |
|
||||||
|
| `asyncHandler` | `async (req, res` |
|
||||||
|
|
||||||
## What it imports / requires
|
## What it imports / requires
|
||||||
|
|
||||||
|
|
@ -38,7 +40,7 @@ _No top-level functions extracted._
|
||||||
|
|
||||||
## Call graph (identifiers invoked)
|
## Call graph (identifiers invoked)
|
||||||
|
|
||||||
`post`, `asyncHandler`, `async`, `from`, `createTimestamp`, `toString`, `slice`, `putBlob`, `status`, `json`, `get`, `readBlobPlaintext`, `createShare`, `redeemShare`
|
`post`, `asyncHandler`, `async`, `from`, `createTimestamp`, `toString`, `slice`, `putBlob`, `status`, `json`, `get`, `readBlobPlaintext`, `isArray`, `putTree`, `map`, `createTreeShare`, `createShare`, `redeemShare`
|
||||||
|
|
||||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
**Package:** `verae-zapier-middleware`
|
**Package:** `verae-zapier-middleware`
|
||||||
**Source:** `packages/verae-zapier-middleware/src/store/blobs.js`
|
**Source:** `packages/verae-zapier-middleware/src/store/blobs.js`
|
||||||
**Lines:** 137
|
**Lines:** 209
|
||||||
|
|
||||||
## What this module is
|
## What this module is
|
||||||
|
|
||||||
|
|
@ -10,7 +10,7 @@ Implementation module in `verae-zapier-middleware`. The tables below are extract
|
||||||
|
|
||||||
## Exports
|
## Exports
|
||||||
|
|
||||||
`decryptBlob`, `putBlob`, `getBlobRecord`, `readBlobPlaintext`, `createShare`, `redeemShare`
|
`decryptBlob`, `putBlob`, `getBlobRecord`, `readBlobPlaintext`, `createShare`, `redeemShare`, `putTree`, `getTree`, `createTreeShare`
|
||||||
|
|
||||||
## Types / interfaces / classes
|
## Types / interfaces / classes
|
||||||
|
|
||||||
|
|
@ -28,6 +28,9 @@ _None extracted._
|
||||||
| `readBlobPlaintext` | `id, tenantId` | id: `string`, tenantId: `string` | `unknown` | see Call graph |
|
| `readBlobPlaintext` | `id, tenantId` | id: `string`, tenantId: `string` | `unknown` | see Call graph |
|
||||||
| `createShare` | `{ blobId, fromTenantId, label }` | — | `unknown` | see Call graph |
|
| `createShare` | `{ blobId, fromTenantId, label }` | — | `unknown` | see Call graph |
|
||||||
| `redeemShare` | `token` | token: `string` | `unknown` | see Call graph |
|
| `redeemShare` | `token` | token: `string` | `unknown` | see Call graph |
|
||||||
|
| `putTree` | `{ tenantId, prefix, files }` | — | `unknown` | see Call graph |
|
||||||
|
| `getTree` | `id` | — | `unknown` | see Call graph |
|
||||||
|
| `createTreeShare` | `{ treeId, fromTenantId, label }` | — | `unknown` | see Call graph |
|
||||||
|
|
||||||
## Methods (class / object)
|
## Methods (class / object)
|
||||||
|
|
||||||
|
|
@ -35,6 +38,8 @@ _None extracted._
|
||||||
|------|------------|
|
|------|------------|
|
||||||
| `persist` | `(none)` |
|
| `persist` | `(none)` |
|
||||||
| `persist` | `(none)` |
|
| `persist` | `(none)` |
|
||||||
|
| `persist` | `(none)` |
|
||||||
|
| `persist` | `(none)` |
|
||||||
|
|
||||||
## What it imports / requires
|
## What it imports / requires
|
||||||
|
|
||||||
|
|
@ -43,7 +48,7 @@ _None extracted._
|
||||||
|
|
||||||
## Call graph (identifiers invoked)
|
## Call graph (identifiers invoked)
|
||||||
|
|
||||||
`ensure`, `getStore`, `encrypt`, `randomBytes`, `createCipheriv`, `concat`, `update`, `final`, `getAuthTag`, `toString`, `decryptBlob`, `createDecipheriv`, `from`, `setAuthTag`, `putBlob`, `randomUUID`, `toISOString`, `persist`, `getBlobRecord`, `readBlobPlaintext`, `createShare`, `redeemShare`
|
`ensure`, `getStore`, `encrypt`, `randomBytes`, `createCipheriv`, `concat`, `update`, `final`, `getAuthTag`, `toString`, `decryptBlob`, `createDecipheriv`, `from`, `setAuthTag`, `putBlob`, `randomUUID`, `toISOString`, `persist`, `getBlobRecord`, `readBlobPlaintext`, `createShare`, `redeemShare`, `map`, `putTree`, `split`, `pop`, `push`, `getTree`, `createTreeShare`
|
||||||
|
|
||||||
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
Each identifier is a call site in this file. Follow the import list to see the defining module; open that module’s MD for parameter and return types.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
**Package:** `verae-zapier-middleware`
|
**Package:** `verae-zapier-middleware`
|
||||||
**Source:** `packages/verae-zapier-middleware/src/store/db.js`
|
**Source:** `packages/verae-zapier-middleware/src/store/db.js`
|
||||||
**Lines:** 117
|
**Lines:** 118
|
||||||
|
|
||||||
## What this module is
|
## What this module is
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
**Package:** `zappier`
|
**Package:** `zappier`
|
||||||
**Source:** `packages/zappier/src/app.ts`
|
**Source:** `packages/zappier/src/app.ts`
|
||||||
**Lines:** 237
|
**Lines:** 257
|
||||||
|
|
||||||
## What this module is
|
## What this module is
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
**Package:** `zappier`
|
**Package:** `zappier`
|
||||||
**Source:** `packages/zappier/src/pricing.ts`
|
**Source:** `packages/zappier/src/pricing.ts`
|
||||||
**Lines:** 173
|
**Lines:** 174
|
||||||
|
|
||||||
## What this module is
|
## What this module is
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,16 +25,18 @@ services:
|
||||||
- nats
|
- nats
|
||||||
|
|
||||||
zappier:
|
zappier:
|
||||||
image: node:20
|
build: ../packages/zappier
|
||||||
working_dir: /app
|
|
||||||
volumes:
|
|
||||||
- ../packages/zappier:/app
|
|
||||||
command: ["npx", "ts-node", "src/index.ts"]
|
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
environment:
|
environment:
|
||||||
PORT: "3000"
|
PORT: "3000"
|
||||||
ZAPPIER_DB: /app/data/zappier.db
|
ZAPPIER_DB: /data/zappier.db
|
||||||
ADMIN_KEY: ${ADMIN_KEY:-admin-dev-key}
|
ADMIN_KEY: ${ADMIN_KEY:-admin-dev-key}
|
||||||
|
VERAE_MIDDLEWARE_URL: http://middleware:3100
|
||||||
|
volumes:
|
||||||
|
- zappier-data:/data
|
||||||
depends_on:
|
depends_on:
|
||||||
- middleware
|
- middleware
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
zappier-data:
|
||||||
|
|
|
||||||
54
packages/verae-activate/creates/timestamp.js
Normal file
54
packages/verae-activate/creates/timestamp.js
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
/**
|
||||||
|
* Create Timestamp — hosted if api_base+api_key, else local mock job.
|
||||||
|
* @module creates/timestamp
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { randomUUID } = require('node:crypto');
|
||||||
|
|
||||||
|
const perform = async (z, bundle) => {
|
||||||
|
const data = String(bundle.inputData.data ?? '');
|
||||||
|
const sha256 = bundle.inputData.sha256;
|
||||||
|
const base = (bundle.authData.api_base || process.env.ZAPPIER_API_BASE || '').replace(/\/$/, '');
|
||||||
|
const key = bundle.authData.api_key || '';
|
||||||
|
if (base && key) {
|
||||||
|
const response = await z.request({
|
||||||
|
method: 'POST',
|
||||||
|
url: `${base}/v1/timestamp`,
|
||||||
|
headers: { 'x-api-key': key, 'content-type': 'application/json' },
|
||||||
|
body: { data, sha256, hashAlg: bundle.inputData.hashAlg },
|
||||||
|
});
|
||||||
|
return { ...(response.data || {}), mode: 'hosted' };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
jobId: randomUUID(),
|
||||||
|
sha256: sha256 || null,
|
||||||
|
existing: false,
|
||||||
|
mode: 'local-mock',
|
||||||
|
note: 'No API configured — mock jobId only. Set API base + key to hit zappier /v1/timestamp.',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
key: 'create_timestamp',
|
||||||
|
noun: 'Timestamp',
|
||||||
|
display: {
|
||||||
|
label: 'Create Timestamp',
|
||||||
|
description: 'Registers data/SHA256. Local mock if no API; hosted zappier when connected.',
|
||||||
|
},
|
||||||
|
operation: {
|
||||||
|
cleanInputData: false,
|
||||||
|
inputFields: [
|
||||||
|
{ key: 'data', label: 'Data', type: 'text', required: false },
|
||||||
|
{ key: 'sha256', label: 'SHA256 hex', type: 'string', required: false },
|
||||||
|
{ key: 'hashAlg', label: 'Hash algorithm', type: 'string', required: false, default: 'SHA256' },
|
||||||
|
],
|
||||||
|
perform,
|
||||||
|
sample: { jobId: '00000000-0000-4000-8000-000000000000', existing: false, mode: 'local-mock' },
|
||||||
|
outputFields: [
|
||||||
|
{ key: 'jobId', type: 'string' },
|
||||||
|
{ key: 'sha256', type: 'string' },
|
||||||
|
{ key: 'existing', type: 'boolean' },
|
||||||
|
{ key: 'mode', type: 'string' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -11,6 +11,7 @@ const authentication = require('./authentication');
|
||||||
const addNumbers = require('./creates/add_numbers');
|
const addNumbers = require('./creates/add_numbers');
|
||||||
const echo = require('./creates/echo');
|
const echo = require('./creates/echo');
|
||||||
const sha256Hash = require('./creates/sha256');
|
const sha256Hash = require('./creates/sha256');
|
||||||
|
const createTimestamp = require('./creates/timestamp');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
version: require('./package.json').version,
|
version: require('./package.json').version,
|
||||||
|
|
@ -20,5 +21,6 @@ module.exports = {
|
||||||
[addNumbers.key]: addNumbers,
|
[addNumbers.key]: addNumbers,
|
||||||
[echo.key]: echo,
|
[echo.key]: echo,
|
||||||
[sha256Hash.key]: sha256Hash,
|
[sha256Hash.key]: sha256Hash,
|
||||||
|
[createTimestamp.key]: createTimestamp,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ describe('verae-activate app', () => {
|
||||||
assert.ok(App.creates.add_numbers);
|
assert.ok(App.creates.add_numbers);
|
||||||
assert.ok(App.creates.echo);
|
assert.ok(App.creates.echo);
|
||||||
assert.ok(App.creates.sha256_hash);
|
assert.ok(App.creates.sha256_hash);
|
||||||
|
assert.ok(App.creates.create_timestamp);
|
||||||
assert.equal(addNumbers.key, 'add_numbers');
|
assert.equal(addNumbers.key, 'add_numbers');
|
||||||
assert.ok(addNumbers.operation.inputFields.some((f) => f.key === 'number1'));
|
assert.ok(addNumbers.operation.inputFields.some((f) => f.key === 'number1'));
|
||||||
assert.ok(addNumbers.operation.inputFields.some((f) => f.key === 'number2'));
|
assert.ok(addNumbers.operation.inputFields.some((f) => f.key === 'number2'));
|
||||||
|
|
@ -65,6 +66,16 @@ describe('verae-activate app', () => {
|
||||||
assert.equal(result.sum, 5);
|
assert.equal(result.sum, 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('create timestamp local mock without API', async () => {
|
||||||
|
const ts = require('../creates/timestamp');
|
||||||
|
const out = await ts.operation.perform(
|
||||||
|
{ request: async () => { throw new Error('no http'); } },
|
||||||
|
{ inputData: { data: 'x' }, authData: {} },
|
||||||
|
);
|
||||||
|
assert.equal(out.mode, 'local-mock');
|
||||||
|
assert.ok(out.jobId);
|
||||||
|
});
|
||||||
|
|
||||||
it('sha256 hashes hello', async () => {
|
it('sha256 hashes hello', async () => {
|
||||||
const sha = require('../creates/sha256');
|
const sha = require('../creates/sha256');
|
||||||
const out = await sha.operation.perform({}, { inputData: { text: 'hello' }, authData: {} });
|
const out = await sha.operation.perform({}, { inputData: { text: 'hello' }, authData: {} });
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ import { createDebugger } from './debug/logger.js';
|
||||||
import { traceMiddleware } from './debug/trace.js';
|
import { traceMiddleware } from './debug/trace.js';
|
||||||
import { apiRoutes } from './routes/index.js';
|
import { apiRoutes } from './routes/index.js';
|
||||||
import { loadStore } from './store/db.js';
|
import { loadStore } from './store/db.js';
|
||||||
|
import { config } from './config.js';
|
||||||
|
import { isNatsConnected } from './nats/connection.js';
|
||||||
|
|
||||||
const log = createDebugger('app');
|
const log = createDebugger('app');
|
||||||
|
|
||||||
|
|
@ -36,6 +38,9 @@ export function createApp(options = {}) {
|
||||||
res.json({
|
res.json({
|
||||||
status: 'ok',
|
status: 'ok',
|
||||||
service: 'verae-zapier-middleware',
|
service: 'verae-zapier-middleware',
|
||||||
|
mockVerae: config.mockVerae,
|
||||||
|
natsEnabled: config.natsEnabled,
|
||||||
|
natsConnected: isNatsConnected(),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ import {
|
||||||
readBlobPlaintext,
|
readBlobPlaintext,
|
||||||
createShare,
|
createShare,
|
||||||
redeemShare,
|
redeemShare,
|
||||||
|
putTree,
|
||||||
|
createTreeShare,
|
||||||
} from '../store/blobs.js';
|
} from '../store/blobs.js';
|
||||||
import { createTimestamp } from '../services/timestampService.js';
|
import { createTimestamp } from '../services/timestampService.js';
|
||||||
|
|
||||||
|
|
@ -65,6 +67,39 @@ objectRoutes.get(
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
objectRoutes.post(
|
||||||
|
'/trees',
|
||||||
|
asyncHandler(async (req, res) => {
|
||||||
|
const { prefix, files } = req.body ?? {};
|
||||||
|
if (!Array.isArray(files) || files.length === 0) {
|
||||||
|
throw new AppError('files array is required', { status: 400, code: 'VALIDATION_ERROR' });
|
||||||
|
}
|
||||||
|
const tree = putTree({
|
||||||
|
tenantId: req.auth.tenantId,
|
||||||
|
prefix: prefix || '/',
|
||||||
|
files: files.map((f) => ({
|
||||||
|
path: String(f.path || f.filename || 'file.bin'),
|
||||||
|
bytes: Buffer.from(String(f.contentBase64 || f.data || ''), f.contentBase64 ? 'base64' : 'utf8'),
|
||||||
|
sha256: f.sha256,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
res.status(201).json(tree);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
objectRoutes.post(
|
||||||
|
'/trees/:id/share',
|
||||||
|
asyncHandler(async (req, res) => {
|
||||||
|
const share = createTreeShare({
|
||||||
|
treeId: req.params.id,
|
||||||
|
fromTenantId: req.auth.tenantId,
|
||||||
|
label: req.body?.label,
|
||||||
|
});
|
||||||
|
if (!share) throw new AppError('Tree not found', { status: 404, code: 'NOT_FOUND' });
|
||||||
|
res.status(201).json(share);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
objectRoutes.post(
|
objectRoutes.post(
|
||||||
'/:id/share',
|
'/:id/share',
|
||||||
asyncHandler(async (req, res) => {
|
asyncHandler(async (req, res) => {
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ function ensure() {
|
||||||
const store = getStore();
|
const store = getStore();
|
||||||
store.blobs ??= {};
|
store.blobs ??= {};
|
||||||
store.shares ??= {};
|
store.shares ??= {};
|
||||||
|
store.trees ??= {};
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,6 +104,7 @@ export function createShare({ blobId, fromTenantId, label }) {
|
||||||
const store = ensure();
|
const store = ensure();
|
||||||
store.shares[token] = {
|
store.shares[token] = {
|
||||||
token,
|
token,
|
||||||
|
kind: 'file',
|
||||||
blobId,
|
blobId,
|
||||||
fromTenantId,
|
fromTenantId,
|
||||||
label: label ?? rec.filename,
|
label: label ?? rec.filename,
|
||||||
|
|
@ -120,10 +122,35 @@ export function redeemShare(token) {
|
||||||
const store = ensure();
|
const store = ensure();
|
||||||
const share = store.shares[token];
|
const share = store.shares[token];
|
||||||
if (!share) return null;
|
if (!share) return null;
|
||||||
|
if (share.kind === 'tree') {
|
||||||
|
const tree = store.trees[share.treeId];
|
||||||
|
if (!tree) return null;
|
||||||
|
return {
|
||||||
|
token,
|
||||||
|
kind: 'tree',
|
||||||
|
prefix: tree.prefix,
|
||||||
|
files: tree.entries.map((e) => {
|
||||||
|
const rec = store.blobs[e.blobId];
|
||||||
|
return rec
|
||||||
|
? {
|
||||||
|
path: e.path,
|
||||||
|
blobId: rec.id,
|
||||||
|
filename: rec.filename,
|
||||||
|
size: rec.size,
|
||||||
|
sha256: rec.sha256,
|
||||||
|
enc: rec.enc,
|
||||||
|
dek: rec.dek,
|
||||||
|
}
|
||||||
|
: { path: e.path, missing: true };
|
||||||
|
}),
|
||||||
|
note: 'Mock tree share: each file includes dek. Production must wrap per-file deks to recipient pubkey.',
|
||||||
|
};
|
||||||
|
}
|
||||||
const rec = store.blobs[share.blobId];
|
const rec = store.blobs[share.blobId];
|
||||||
if (!rec) return null;
|
if (!rec) return null;
|
||||||
return {
|
return {
|
||||||
token,
|
token,
|
||||||
|
kind: 'file',
|
||||||
blobId: rec.id,
|
blobId: rec.id,
|
||||||
filename: rec.filename,
|
filename: rec.filename,
|
||||||
size: rec.size,
|
size: rec.size,
|
||||||
|
|
@ -134,3 +161,48 @@ export function redeemShare(token) {
|
||||||
note: 'Mock share: dek is included so the recipient can decrypt. Production must wrap dek to recipient pubkey.',
|
note: 'Mock share: dek is included so the recipient can decrypt. Production must wrap dek to recipient pubkey.',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{ tenantId: string, prefix: string, files: Array<{ path: string, bytes: Buffer, sha256?: string }> }} params
|
||||||
|
*/
|
||||||
|
export function putTree({ tenantId, prefix, files }) {
|
||||||
|
const store = ensure();
|
||||||
|
const id = randomUUID();
|
||||||
|
const entries = [];
|
||||||
|
for (const file of files) {
|
||||||
|
const stored = putBlob({
|
||||||
|
tenantId,
|
||||||
|
sha256: file.sha256,
|
||||||
|
filename: file.path.split('/').pop(),
|
||||||
|
bytes: file.bytes,
|
||||||
|
});
|
||||||
|
entries.push({ path: file.path, blobId: stored.id });
|
||||||
|
}
|
||||||
|
store.trees[id] = { id, tenantId, prefix: prefix || '/', entries, createdAt: new Date().toISOString() };
|
||||||
|
persist();
|
||||||
|
return { id, prefix: store.trees[id].prefix, fileCount: entries.length };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTree(id) {
|
||||||
|
return ensure().trees[id] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{ treeId: string, fromTenantId: string, label?: string }} params
|
||||||
|
*/
|
||||||
|
export function createTreeShare({ treeId, fromTenantId, label }) {
|
||||||
|
const tree = getTree(treeId);
|
||||||
|
if (!tree || tree.tenantId !== fromTenantId) return null;
|
||||||
|
const token = `shr_${randomBytes(16).toString('hex')}`;
|
||||||
|
const store = ensure();
|
||||||
|
store.shares[token] = {
|
||||||
|
token,
|
||||||
|
kind: 'tree',
|
||||||
|
treeId,
|
||||||
|
fromTenantId,
|
||||||
|
label: label ?? tree.prefix,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
persist();
|
||||||
|
return { token, kind: 'tree', treeId, label: store.shares[token].label, fileCount: tree.entries.length };
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ export function emptyStore() {
|
||||||
jobWatchers: [],
|
jobWatchers: [],
|
||||||
blobs: {},
|
blobs: {},
|
||||||
shares: {},
|
shares: {},
|
||||||
|
trees: {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,41 @@ describe('encrypted objects + share + pdf receipt', () => {
|
||||||
assert.equal(redeemed.body.publicMetadata.title, 'pub');
|
assert.equal(redeemed.body.publicMetadata.title, 'pub');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('shares a directory tree of encrypted files', async () => {
|
||||||
|
const tree = await call(port, 'POST', '/zapier/v1/objects/trees', ownerKey, {
|
||||||
|
prefix: 'docs/',
|
||||||
|
files: [
|
||||||
|
{ path: 'docs/a.txt', data: 'alpha' },
|
||||||
|
{ path: 'docs/b.txt', data: 'beta' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
assert.equal(tree.status, 201);
|
||||||
|
assert.equal(tree.body.fileCount, 2);
|
||||||
|
const share = await call(port, 'POST', `/zapier/v1/objects/trees/${tree.body.id}/share`, ownerKey, {});
|
||||||
|
assert.equal(share.status, 201);
|
||||||
|
assert.equal(share.body.kind, 'tree');
|
||||||
|
const redeemed = await call(port, 'GET', `/zapier/v1/shares/${share.body.token}`, otherKey);
|
||||||
|
assert.equal(redeemed.status, 200);
|
||||||
|
assert.equal(redeemed.body.kind, 'tree');
|
||||||
|
assert.equal(redeemed.body.files.length, 2);
|
||||||
|
assert.ok(redeemed.body.files[0].dek);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('hash lookup never includes privateMetadata', async () => {
|
||||||
|
const created = await call(port, 'POST', '/zapier/v1/timestamp', ownerKey, {
|
||||||
|
data: 'meta-private',
|
||||||
|
publicMetadata: { vis: 'yes' },
|
||||||
|
privateMetadata: { secret: 'nope' },
|
||||||
|
});
|
||||||
|
const sha = created.body.sha256;
|
||||||
|
const look = await call(port, 'GET', `/zapier/v1/hashes/${sha}`, ownerKey);
|
||||||
|
assert.equal(look.status, 200);
|
||||||
|
assert.equal(look.body.publicMetadata.vis, 'yes');
|
||||||
|
assert.equal(look.body.privateMetadata, undefined);
|
||||||
|
const st = await call(port, 'GET', `/zapier/v1/status/${created.body.jobId}`, ownerKey);
|
||||||
|
assert.equal(st.body.privateMetadata.secret, 'nope');
|
||||||
|
});
|
||||||
|
|
||||||
it('returns a PDF receipt starting with %PDF', async () => {
|
it('returns a PDF receipt starting with %PDF', async () => {
|
||||||
const wait = await call(port, 'POST', '/zapier/v1/timestamp/wait', ownerKey, { data: 'pdf-receipt' });
|
const wait = await call(port, 'POST', '/zapier/v1/timestamp/wait', ownerKey, { data: 'pdf-receipt' });
|
||||||
const jobId = wait.body.id;
|
const jobId = wait.body.id;
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,9 @@ describe('createApp', () => {
|
||||||
const body = await res.json();
|
const body = await res.json();
|
||||||
assert.equal(body.status, 'ok');
|
assert.equal(body.status, 'ok');
|
||||||
assert.equal(body.service, 'verae-zapier-middleware');
|
assert.equal(body.service, 'verae-zapier-middleware');
|
||||||
|
assert.equal(typeof body.mockVerae, 'boolean');
|
||||||
|
assert.equal(typeof body.natsEnabled, 'boolean');
|
||||||
|
assert.equal(typeof body.natsConnected, 'boolean');
|
||||||
assert.ok(res.headers.get('x-trace-id'));
|
assert.ok(res.headers.get('x-trace-id'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
12
packages/zappier/Dockerfile
Normal file
12
packages/zappier/Dockerfile
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
FROM node:20-bookworm-slim
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY tsconfig.json ./
|
||||||
|
COPY src ./src
|
||||||
|
COPY admin ./admin
|
||||||
|
COPY portal ./portal
|
||||||
|
COPY openapi.yaml ./
|
||||||
|
ENV PORT=3000
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD ["npx", "ts-node", "src/index.ts"]
|
||||||
|
|
@ -93,6 +93,21 @@ paths:
|
||||||
description: OK
|
description: OK
|
||||||
'404':
|
'404':
|
||||||
description: Missing
|
description: Missing
|
||||||
|
/v1/receipts/{jobId}:
|
||||||
|
get:
|
||||||
|
operationId: receipt
|
||||||
|
summary: Retrieval receipt JSON (mock job ids from /v1/timestamp)
|
||||||
|
parameters:
|
||||||
|
- name: jobId
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
'404':
|
||||||
|
description: Missing
|
||||||
/v1/add:
|
/v1/add:
|
||||||
post:
|
post:
|
||||||
operationId: add
|
operationId: add
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,7 @@ export function buildApp(deps: AppDeps = {}): {
|
||||||
string,
|
string,
|
||||||
{ jobId: string; sha256: string; data?: string; timestamp: string }
|
{ jobId: string; sha256: string; data?: string; timestamp: string }
|
||||||
>();
|
>();
|
||||||
|
const jobIndex = new Map<string, { jobId: string; sha256: string; data?: string; timestamp: string }>();
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
@ -177,10 +178,29 @@ export function buildApp(deps: AppDeps = {}): {
|
||||||
}
|
}
|
||||||
const jobId = randomUUID();
|
const jobId = randomUUID();
|
||||||
const timestamp = new Date().toISOString();
|
const timestamp = new Date().toISOString();
|
||||||
hashIndex.set(sha256, { jobId, sha256, data, timestamp });
|
const rec = { jobId, sha256, data, timestamp };
|
||||||
|
hashIndex.set(sha256, rec);
|
||||||
|
jobIndex.set(jobId, rec);
|
||||||
res.status(202).json({ jobId, sha256, existing: false, timestamp });
|
res.status(202).json({ jobId, sha256, existing: false, timestamp });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get('/v1/receipts/:jobId', meter('receipt', usage, pricing), (req, res) => {
|
||||||
|
const rec = jobIndex.get(String(req.params.jobId));
|
||||||
|
if (!rec) {
|
||||||
|
res.status(404).json({ error: 'Job not found' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.json({
|
||||||
|
type: 'verae.retrieval-receipt',
|
||||||
|
format: 'json',
|
||||||
|
jobId: rec.jobId,
|
||||||
|
sha256: rec.sha256,
|
||||||
|
timestamp: rec.timestamp,
|
||||||
|
extraSeal: { event: 'document.retrieved', retrievedAt: new Date().toISOString() },
|
||||||
|
quote: res.locals.quote,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
app.get('/v1/hashes/:sha256', meter('hash-lookup', usage, pricing), (req, res) => {
|
app.get('/v1/hashes/:sha256', meter('hash-lookup', usage, pricing), (req, res) => {
|
||||||
const sha256 = String(req.params.sha256 || '').toLowerCase();
|
const sha256 = String(req.params.sha256 || '').toLowerCase();
|
||||||
const rec = hashIndex.get(sha256);
|
const rec = hashIndex.get(sha256);
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ export const DEFAULT_RATE_CARD: RateCard = {
|
||||||
add: { kind: 'free' },
|
add: { kind: 'free' },
|
||||||
timestamp: { kind: 'fixed', fixedCents: 4 },
|
timestamp: { kind: 'fixed', fixedCents: 4 },
|
||||||
'hash-lookup': { kind: 'free' },
|
'hash-lookup': { kind: 'free' },
|
||||||
|
receipt: { kind: 'free' },
|
||||||
storage: { kind: 'variable', baseCents: 10, perKbCents: 1, perMbCents: 50 },
|
storage: { kind: 'variable', baseCents: 10, perKbCents: 1, perMbCents: 50 },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,11 @@ describe('Zappier API', () => {
|
||||||
.set('x-api-key', KEY);
|
.set('x-api-key', KEY);
|
||||||
expect(look.status).toBe(200);
|
expect(look.status).toBe(200);
|
||||||
expect(look.body.jobId).toBe(a.body.jobId);
|
expect(look.body.jobId).toBe(a.body.jobId);
|
||||||
|
const receipt = await request(app)
|
||||||
|
.get(`/v1/receipts/${a.body.jobId}`)
|
||||||
|
.set('x-api-key', KEY);
|
||||||
|
expect(receipt.status).toBe(200);
|
||||||
|
expect(receipt.body.type).toBe('verae.retrieval-receipt');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('POST /v1/add returns the sum and is free', async () => {
|
it('POST /v1/add returns the sum and is free', async () => {
|
||||||
|
|
|
||||||
22
scripts/seed-demo.mjs
Normal file
22
scripts/seed-demo.mjs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Seed a demo tenant on local middleware (MOCK_VERAE).
|
||||||
|
*/
|
||||||
|
const base = process.env.MIDDLEWARE_URL || 'http://127.0.0.1:3100';
|
||||||
|
|
||||||
|
const res = await fetch(`${base}/zapier/v1/signup`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: 'demo@veraetime.local',
|
||||||
|
name: 'Demo tenant',
|
||||||
|
veraeUsername: 'demouser',
|
||||||
|
veraePassword: 'demopass',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const body = await res.json();
|
||||||
|
if (!res.ok) {
|
||||||
|
console.error(body);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log(JSON.stringify({ apiKey: body.apiKey, tenant: body.tenant, middleware: base }, null, 2));
|
||||||
Loading…
Add table
Add a link
Reference in a new issue