@promptowl/contextnest-community 0.1.0-alpha.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONFIGURATION.md +118 -118
- package/dist/{chunk-Q2DCOS7V.js → chunk-2FXVMVZJ.js} +53 -4
- package/dist/{chunk-USIDOGVJ.js → chunk-2TW25QEA.js} +79 -3
- package/dist/{chunk-P6NG56CO.js → chunk-BLOPZDPL.js} +25 -2
- package/dist/{chunk-DJFEV4ET.js → chunk-XDCW4HTW.js} +2 -2
- package/dist/index.js +1014 -326
- package/dist/{review-service-5CLVZKAR.js → review-service-2JHZHZWJ.js} +3 -3
- package/dist/{stewardship-service-NC67XBYO.js → stewardship-service-ZJATH6OM.js} +2 -2
- package/dist/{version-service-Z6FYJRAG.js → version-service-2MZJGE3H.js} +4 -2
- package/dist/web3/assets/index-BlGzOlFt.css +1 -0
- package/dist/web3/assets/index-C3W5d7fT.js +591 -0
- package/dist/web3/index.html +2 -2
- package/package.json +125 -108
- package/dist/web3/assets/index-CemroDXg.css +0 -1
- package/dist/web3/assets/index-xLLf4lHJ.js +0 -332
package/CONFIGURATION.md
CHANGED
|
@@ -1,118 +1,118 @@
|
|
|
1
|
-
# Configuration
|
|
2
|
-
|
|
3
|
-
All server configuration is driven by environment variables. Set them in your shell, in a `.env` file, in a Docker image, or via your process manager — however you deploy the rest of your stack.
|
|
4
|
-
|
|
5
|
-
## Auth modes
|
|
6
|
-
|
|
7
|
-
The server supports two authentication modes. You pick one per instance with `AUTH_MODE`.
|
|
8
|
-
|
|
9
|
-
### `AUTH_MODE=key` (default)
|
|
10
|
-
|
|
11
|
-
Every request requires an `Authorization: Bearer cnst_...` header. Use this for any multi-user or internet-facing deployment.
|
|
12
|
-
|
|
13
|
-
- Users register via `POST /auth/register` (email + password) or log in with PromptOwl via `POST /auth/device` → `POST /auth/promptowl`.
|
|
14
|
-
- API keys are per-user (`cnst_<64-hex>`) and can be scoped to a single nest for service-account use.
|
|
15
|
-
- Rate-limited login / register / device-auth endpoints (sliding window, per IP + per email).
|
|
16
|
-
- First PromptOwl-authenticated user becomes the server admin (atomic claim). Admin can invite teammates at `POST /auth/invite`.
|
|
17
|
-
|
|
18
|
-
Clients include the token on every request:
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
curl -H 'Authorization: Bearer cnst_<your-token>' http://your-server/nests
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
### `AUTH_MODE=open`
|
|
25
|
-
|
|
26
|
-
No authentication required. Every request is attributed to the anonymous admin user (`00000000-...`). Everyone effectively owns every anon-created nest.
|
|
27
|
-
|
|
28
|
-
**Use this only when:**
|
|
29
|
-
- You're running locally for yourself (`bind 127.0.0.1`), or
|
|
30
|
-
- You're on a trusted LAN and don't care about access controls, or
|
|
31
|
-
- You're behind an upstream reverse proxy that already authenticates.
|
|
32
|
-
|
|
33
|
-
**Don't use this when:**
|
|
34
|
-
- The port is reachable from the public internet
|
|
35
|
-
- Multiple people share the deployment and need isolated data
|
|
36
|
-
- You need audit trails (every write shows up under "anonymous admin")
|
|
37
|
-
|
|
38
|
-
The server prints a loud warning at startup when `AUTH_MODE=open` is active.
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
## Full env var reference
|
|
43
|
-
|
|
44
|
-
| Var | Default | Purpose |
|
|
45
|
-
|---|---|---|
|
|
46
|
-
| `PORT` | `3838` | HTTP port the server listens on. |
|
|
47
|
-
| `DATA_ROOT` | `./data` (relative to cwd) | Root directory for SQLite DB + nest filesystem vaults. Set to an absolute path in production so it's independent of where you `cd`'d from. |
|
|
48
|
-
| `DATABASE_PATH` | `$DATA_ROOT/community.db` | Override the SQLite file location explicitly. |
|
|
49
|
-
| `AUTH_MODE` | `key` | `key` or `open`. See above. |
|
|
50
|
-
| `PROMPTOWL_API_URL` | `https://app.promptowl.ai` | PromptOwl's API origin — used for device auth, license validation, telemetry. Override for air-gapped or test setups. |
|
|
51
|
-
| `PROMPTOWL_KEY` | `""` | Your PromptOwl Community Server license key (`pk_...`). Unlicensed instances still run but some features may be limited. |
|
|
52
|
-
| `TELEMETRY_ENABLED` | `"true"` (set to `"false"` to disable) | Batched, anonymized usage events sent to PromptOwl. Off disables the loop entirely. |
|
|
53
|
-
| `TELEMETRY_INTERVAL_MS` | `3600000` (1 hour) | How often buffered telemetry is flushed to PromptOwl. |
|
|
54
|
-
| `CORS_ORIGINS` | `*` in open mode; `http://localhost:5173,http://localhost:3838` in key mode | Comma-separated allowlist. Set to `*` to allow any origin (**only** safe in open mode — in key mode with Bearer tokens this enables CSRF). |
|
|
55
|
-
| `MAX_BODY_BYTES` | `10485760` (10 MB) | Reject requests whose `Content-Length` exceeds this. Prevents giant-payload DoS. |
|
|
56
|
-
|
|
57
|
-
---
|
|
58
|
-
|
|
59
|
-
## Typical deployments
|
|
60
|
-
|
|
61
|
-
### Local dev / single user
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
AUTH_MODE=open DATA_ROOT=./my-data pnpm dev
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
Or the default dev mode if `pnpm dev` already sets `AUTH_MODE=open` in your scripts.
|
|
68
|
-
|
|
69
|
-
### Team / multi-user behind a reverse proxy
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
AUTH_MODE=key \
|
|
73
|
-
CORS_ORIGINS="https://team.example.com,https://admin.example.com" \
|
|
74
|
-
DATA_ROOT=/var/lib/contextnest \
|
|
75
|
-
PROMPTOWL_KEY=pk_... \
|
|
76
|
-
pnpm start
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
Terminate TLS at the proxy, forward `X-Forwarded-For` and `X-Real-IP` headers (the rate limiter reads them), and bind the server to `127.0.0.1` so only the proxy can reach it.
|
|
80
|
-
|
|
81
|
-
### Hosted / commercial SaaS
|
|
82
|
-
|
|
83
|
-
Same as team, plus:
|
|
84
|
-
- Run N instances behind a load balancer sharing a network-attached `DATA_ROOT` (note: SQLite + shared filesystem is not a great long-term story — migrate to the MongoDB storage adapter when scaling beyond one box).
|
|
85
|
-
- Set `TELEMETRY_ENABLED=true` and a valid `PROMPTOWL_KEY` so usage rolls up to PromptOwl.
|
|
86
|
-
- Rotate keys regularly via `DELETE /auth/keys/:id` + `POST /auth/keys`.
|
|
87
|
-
|
|
88
|
-
---
|
|
89
|
-
|
|
90
|
-
## access.yaml (optional, open + key mode)
|
|
91
|
-
|
|
92
|
-
Drop an `access.yaml` at `$DATA_ROOT/access.yaml` to add a server-level ABAC layer — useful even in key mode for super-admins and group-based defaults.
|
|
93
|
-
|
|
94
|
-
```yaml
|
|
95
|
-
mode: restricted
|
|
96
|
-
allowed_users:
|
|
97
|
-
- "*.acme.com" # email wildcard — anyone @acme.com
|
|
98
|
-
- "partner@vendor.com" # exact match
|
|
99
|
-
super_admins:
|
|
100
|
-
- "ceo@acme.com" # always allowed on every nest
|
|
101
|
-
groups:
|
|
102
|
-
engineering:
|
|
103
|
-
default_permission: write
|
|
104
|
-
members:
|
|
105
|
-
- "*.eng.acme.com"
|
|
106
|
-
viewers:
|
|
107
|
-
default_permission: read
|
|
108
|
-
members:
|
|
109
|
-
- "*.contractor.acme.com"
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
See `STEWARDSHIP.md` for how super-admins and groups interact with per-nest stewardship.
|
|
113
|
-
|
|
114
|
-
---
|
|
115
|
-
|
|
116
|
-
## Reload
|
|
117
|
-
|
|
118
|
-
Changing env vars requires a restart — the server reads them at boot. `tsx watch` in dev will pick up code changes automatically but not env changes.
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
All server configuration is driven by environment variables. Set them in your shell, in a `.env` file, in a Docker image, or via your process manager — however you deploy the rest of your stack.
|
|
4
|
+
|
|
5
|
+
## Auth modes
|
|
6
|
+
|
|
7
|
+
The server supports two authentication modes. You pick one per instance with `AUTH_MODE`.
|
|
8
|
+
|
|
9
|
+
### `AUTH_MODE=key` (default)
|
|
10
|
+
|
|
11
|
+
Every request requires an `Authorization: Bearer cnst_...` header. Use this for any multi-user or internet-facing deployment.
|
|
12
|
+
|
|
13
|
+
- Users register via `POST /auth/register` (email + password) or log in with PromptOwl via `POST /auth/device` → `POST /auth/promptowl`.
|
|
14
|
+
- API keys are per-user (`cnst_<64-hex>`) and can be scoped to a single nest for service-account use.
|
|
15
|
+
- Rate-limited login / register / device-auth endpoints (sliding window, per IP + per email).
|
|
16
|
+
- First PromptOwl-authenticated user becomes the server admin (atomic claim). Admin can invite teammates at `POST /auth/invite`.
|
|
17
|
+
|
|
18
|
+
Clients include the token on every request:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
curl -H 'Authorization: Bearer cnst_<your-token>' http://your-server/nests
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### `AUTH_MODE=open`
|
|
25
|
+
|
|
26
|
+
No authentication required. Every request is attributed to the anonymous admin user (`00000000-...`). Everyone effectively owns every anon-created nest.
|
|
27
|
+
|
|
28
|
+
**Use this only when:**
|
|
29
|
+
- You're running locally for yourself (`bind 127.0.0.1`), or
|
|
30
|
+
- You're on a trusted LAN and don't care about access controls, or
|
|
31
|
+
- You're behind an upstream reverse proxy that already authenticates.
|
|
32
|
+
|
|
33
|
+
**Don't use this when:**
|
|
34
|
+
- The port is reachable from the public internet
|
|
35
|
+
- Multiple people share the deployment and need isolated data
|
|
36
|
+
- You need audit trails (every write shows up under "anonymous admin")
|
|
37
|
+
|
|
38
|
+
The server prints a loud warning at startup when `AUTH_MODE=open` is active.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Full env var reference
|
|
43
|
+
|
|
44
|
+
| Var | Default | Purpose |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| `PORT` | `3838` | HTTP port the server listens on. |
|
|
47
|
+
| `DATA_ROOT` | `./data` (relative to cwd) | Root directory for SQLite DB + nest filesystem vaults. Set to an absolute path in production so it's independent of where you `cd`'d from. |
|
|
48
|
+
| `DATABASE_PATH` | `$DATA_ROOT/community.db` | Override the SQLite file location explicitly. |
|
|
49
|
+
| `AUTH_MODE` | `key` | `key` or `open`. See above. |
|
|
50
|
+
| `PROMPTOWL_API_URL` | `https://app.promptowl.ai` | PromptOwl's API origin — used for device auth, license validation, telemetry. Override for air-gapped or test setups. |
|
|
51
|
+
| `PROMPTOWL_KEY` | `""` | Your PromptOwl Community Server license key (`pk_...`). Unlicensed instances still run but some features may be limited. |
|
|
52
|
+
| `TELEMETRY_ENABLED` | `"true"` (set to `"false"` to disable) | Batched, anonymized usage events sent to PromptOwl. Off disables the loop entirely. |
|
|
53
|
+
| `TELEMETRY_INTERVAL_MS` | `3600000` (1 hour) | How often buffered telemetry is flushed to PromptOwl. |
|
|
54
|
+
| `CORS_ORIGINS` | `*` in open mode; `http://localhost:5173,http://localhost:3838` in key mode | Comma-separated allowlist. Set to `*` to allow any origin (**only** safe in open mode — in key mode with Bearer tokens this enables CSRF). |
|
|
55
|
+
| `MAX_BODY_BYTES` | `10485760` (10 MB) | Reject requests whose `Content-Length` exceeds this. Prevents giant-payload DoS. |
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Typical deployments
|
|
60
|
+
|
|
61
|
+
### Local dev / single user
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
AUTH_MODE=open DATA_ROOT=./my-data pnpm dev
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Or the default dev mode if `pnpm dev` already sets `AUTH_MODE=open` in your scripts.
|
|
68
|
+
|
|
69
|
+
### Team / multi-user behind a reverse proxy
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
AUTH_MODE=key \
|
|
73
|
+
CORS_ORIGINS="https://team.example.com,https://admin.example.com" \
|
|
74
|
+
DATA_ROOT=/var/lib/contextnest \
|
|
75
|
+
PROMPTOWL_KEY=pk_... \
|
|
76
|
+
pnpm start
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Terminate TLS at the proxy, forward `X-Forwarded-For` and `X-Real-IP` headers (the rate limiter reads them), and bind the server to `127.0.0.1` so only the proxy can reach it.
|
|
80
|
+
|
|
81
|
+
### Hosted / commercial SaaS
|
|
82
|
+
|
|
83
|
+
Same as team, plus:
|
|
84
|
+
- Run N instances behind a load balancer sharing a network-attached `DATA_ROOT` (note: SQLite + shared filesystem is not a great long-term story — migrate to the MongoDB storage adapter when scaling beyond one box).
|
|
85
|
+
- Set `TELEMETRY_ENABLED=true` and a valid `PROMPTOWL_KEY` so usage rolls up to PromptOwl.
|
|
86
|
+
- Rotate keys regularly via `DELETE /auth/keys/:id` + `POST /auth/keys`.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## access.yaml (optional, open + key mode)
|
|
91
|
+
|
|
92
|
+
Drop an `access.yaml` at `$DATA_ROOT/access.yaml` to add a server-level ABAC layer — useful even in key mode for super-admins and group-based defaults.
|
|
93
|
+
|
|
94
|
+
```yaml
|
|
95
|
+
mode: restricted
|
|
96
|
+
allowed_users:
|
|
97
|
+
- "*.acme.com" # email wildcard — anyone @acme.com
|
|
98
|
+
- "partner@vendor.com" # exact match
|
|
99
|
+
super_admins:
|
|
100
|
+
- "ceo@acme.com" # always allowed on every nest
|
|
101
|
+
groups:
|
|
102
|
+
engineering:
|
|
103
|
+
default_permission: write
|
|
104
|
+
members:
|
|
105
|
+
- "*.eng.acme.com"
|
|
106
|
+
viewers:
|
|
107
|
+
default_permission: read
|
|
108
|
+
members:
|
|
109
|
+
- "*.contractor.acme.com"
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
See `STEWARDSHIP.md` for how super-admins and groups interact with per-nest stewardship.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Reload
|
|
117
|
+
|
|
118
|
+
Changing env vars requires a restart — the server reads them at boot. `tsx watch` in dev will pick up code changes automatically but not env changes.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
config,
|
|
3
3
|
getDb
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-2TW25QEA.js";
|
|
5
5
|
|
|
6
6
|
// src/governance/stewardship-service.ts
|
|
7
7
|
import { v4 as uuid } from "uuid";
|
|
@@ -170,7 +170,34 @@ function listStewards(params) {
|
|
|
170
170
|
sql += " ORDER BY scope, COALESCE(node_pattern, tag_name, ''), user_email";
|
|
171
171
|
return db.prepare(sql).all(...args).map(rowToSteward);
|
|
172
172
|
}
|
|
173
|
-
function
|
|
173
|
+
function rolePermission(role) {
|
|
174
|
+
return role === "editor" ? "write" : "read";
|
|
175
|
+
}
|
|
176
|
+
async function ensureCollaborator(nestId, email, permission, grantedBy) {
|
|
177
|
+
const db = getDb();
|
|
178
|
+
let userRow = db.prepare("SELECT id FROM users WHERE email = ?").get(email);
|
|
179
|
+
if (!userRow) {
|
|
180
|
+
const { hashPassword } = await import("./keys-YV33AJK3.js");
|
|
181
|
+
const newId = uuid();
|
|
182
|
+
db.prepare(
|
|
183
|
+
"INSERT INTO users (id, email, name, password_hash, is_invited) VALUES (?, ?, ?, ?, 1)"
|
|
184
|
+
).run(newId, email, null, await hashPassword(uuid()));
|
|
185
|
+
userRow = { id: newId };
|
|
186
|
+
}
|
|
187
|
+
const nestRow = db.prepare("SELECT user_id FROM nests WHERE id = ?").get(nestId);
|
|
188
|
+
if (nestRow && nestRow.user_id === userRow.id) return;
|
|
189
|
+
const existing = db.prepare(
|
|
190
|
+
"SELECT id FROM nest_collaborators WHERE nest_id = ? AND user_id = ?"
|
|
191
|
+
).get(nestId, userRow.id);
|
|
192
|
+
if (existing) return;
|
|
193
|
+
const granterRow = db.prepare("SELECT id FROM users WHERE email = ?").get(grantedBy);
|
|
194
|
+
const granterId = granterRow?.id ?? nestRow?.user_id;
|
|
195
|
+
if (!granterId) return;
|
|
196
|
+
db.prepare(
|
|
197
|
+
"INSERT INTO nest_collaborators (id, nest_id, user_id, permission, granted_by) VALUES (?, ?, ?, ?, ?)"
|
|
198
|
+
).run(uuid(), nestId, userRow.id, permission, granterId);
|
|
199
|
+
}
|
|
200
|
+
async function createStewardRecord(params) {
|
|
174
201
|
if (params.users.length === 0) {
|
|
175
202
|
throw new Error("At least one user is required");
|
|
176
203
|
}
|
|
@@ -229,6 +256,12 @@ function createStewardRecord(params) {
|
|
|
229
256
|
isActive: true
|
|
230
257
|
});
|
|
231
258
|
results.push(created);
|
|
259
|
+
await ensureCollaborator(
|
|
260
|
+
params.nestId,
|
|
261
|
+
email,
|
|
262
|
+
rolePermission(user.role),
|
|
263
|
+
params.assignedBy
|
|
264
|
+
);
|
|
232
265
|
}
|
|
233
266
|
db.prepare(
|
|
234
267
|
"UPDATE nests SET stewardship_enabled = 1 WHERE id = ? AND stewardship_enabled = 0"
|
|
@@ -255,7 +288,10 @@ function resolve(nestId, nodeId) {
|
|
|
255
288
|
WHERE s.nest_id = ? AND s.is_active = 1 AND s.scope = 'folder'
|
|
256
289
|
AND s.node_pattern IS NOT NULL
|
|
257
290
|
AND instr(s.node_pattern, '*') = 0
|
|
258
|
-
AND
|
|
291
|
+
AND (
|
|
292
|
+
? LIKE s.node_pattern || '/%'
|
|
293
|
+
OR s.node_pattern = ?
|
|
294
|
+
)
|
|
259
295
|
UNION ALL
|
|
260
296
|
SELECT s.*, 3 AS priority, ('tag: ' || s.tag_name) AS match_source
|
|
261
297
|
FROM stewards s
|
|
@@ -270,7 +306,20 @@ function resolve(nestId, nodeId) {
|
|
|
270
306
|
WHERE s.nest_id = ? AND s.is_active = 1 AND s.scope = 'nest'
|
|
271
307
|
ORDER BY priority ASC, user_email ASC
|
|
272
308
|
`
|
|
273
|
-
).all(
|
|
309
|
+
).all(
|
|
310
|
+
nestId,
|
|
311
|
+
nodeId,
|
|
312
|
+
// document branch
|
|
313
|
+
nestId,
|
|
314
|
+
nodeId,
|
|
315
|
+
nestId,
|
|
316
|
+
// folder branch (path-prefix OR whole-nest folder)
|
|
317
|
+
nestId,
|
|
318
|
+
nodeId,
|
|
319
|
+
// tag branch
|
|
320
|
+
nestId
|
|
321
|
+
// nest branch
|
|
322
|
+
);
|
|
274
323
|
const resolved = rows.map((row) => ({
|
|
275
324
|
steward: rowToSteward(row),
|
|
276
325
|
priority: row.priority,
|
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
// src/config.ts
|
|
2
|
-
import { join } from "path";
|
|
2
|
+
import { join, dirname } from "path";
|
|
3
|
+
import { existsSync } from "fs";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import dotenv from "dotenv";
|
|
6
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
var __dirname = dirname(__filename);
|
|
8
|
+
var envCandidates = [
|
|
9
|
+
join(process.cwd(), ".env"),
|
|
10
|
+
join(__dirname, "..", ".env")
|
|
11
|
+
];
|
|
12
|
+
var envFileLoaded = envCandidates.find((p) => existsSync(p)) || null;
|
|
13
|
+
if (envFileLoaded) {
|
|
14
|
+
dotenv.config({ path: envFileLoaded, override: true });
|
|
15
|
+
}
|
|
16
|
+
console.log(
|
|
17
|
+
`[config] dotenv: ${envFileLoaded ? `loaded ${envFileLoaded}` : "no .env file found"}`
|
|
18
|
+
);
|
|
3
19
|
function dataRoot() {
|
|
4
20
|
return process.env.DATA_ROOT || join(process.cwd(), "data");
|
|
5
21
|
}
|
|
@@ -19,6 +35,14 @@ var config = {
|
|
|
19
35
|
get PROMPTOWL_KEY() {
|
|
20
36
|
return process.env.PROMPTOWL_KEY || "";
|
|
21
37
|
},
|
|
38
|
+
/**
|
|
39
|
+
* Path to the .env file the server reads its config from. Used by
|
|
40
|
+
* the license install flow to persist PROMPTOWL_KEY alongside any
|
|
41
|
+
* existing env vars, instead of a separate sidecar file.
|
|
42
|
+
*/
|
|
43
|
+
get ENV_FILE_PATH() {
|
|
44
|
+
return process.env.ENV_FILE_PATH || join(process.cwd(), ".env");
|
|
45
|
+
},
|
|
22
46
|
get TELEMETRY_ENABLED() {
|
|
23
47
|
return process.env.TELEMETRY_ENABLED !== "false";
|
|
24
48
|
},
|
|
@@ -52,7 +76,7 @@ var config = {
|
|
|
52
76
|
// src/db/client.ts
|
|
53
77
|
import Database from "better-sqlite3";
|
|
54
78
|
import { mkdirSync } from "fs";
|
|
55
|
-
import { dirname } from "path";
|
|
79
|
+
import { dirname as dirname2 } from "path";
|
|
56
80
|
|
|
57
81
|
// src/db/migrations.ts
|
|
58
82
|
function runMigrations(db2) {
|
|
@@ -204,6 +228,9 @@ function runMigrations(db2) {
|
|
|
204
228
|
if (!userCols.includes("is_admin")) {
|
|
205
229
|
db2.exec("ALTER TABLE users ADD COLUMN is_admin INTEGER NOT NULL DEFAULT 0");
|
|
206
230
|
}
|
|
231
|
+
if (!userCols.includes("is_invited")) {
|
|
232
|
+
db2.exec("ALTER TABLE users ADD COLUMN is_invited INTEGER NOT NULL DEFAULT 0");
|
|
233
|
+
}
|
|
207
234
|
db2.exec(`
|
|
208
235
|
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
209
236
|
id TEXT PRIMARY KEY,
|
|
@@ -326,16 +353,65 @@ function runMigrations(db2) {
|
|
|
326
353
|
recordMigration("002_steward_parity");
|
|
327
354
|
})();
|
|
328
355
|
}
|
|
356
|
+
if (!hasMigration("003_sessions_and_single_api_key")) {
|
|
357
|
+
db2.transaction(() => {
|
|
358
|
+
db2.exec(`
|
|
359
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
360
|
+
id TEXT PRIMARY KEY,
|
|
361
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
362
|
+
expires_at TEXT NOT NULL,
|
|
363
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
364
|
+
last_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
365
|
+
user_agent TEXT
|
|
366
|
+
);
|
|
367
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id);
|
|
368
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at);
|
|
369
|
+
`);
|
|
370
|
+
db2.exec(`
|
|
371
|
+
DELETE FROM api_keys
|
|
372
|
+
WHERE id IN (
|
|
373
|
+
SELECT id FROM (
|
|
374
|
+
SELECT
|
|
375
|
+
id,
|
|
376
|
+
ROW_NUMBER() OVER (
|
|
377
|
+
PARTITION BY user_id
|
|
378
|
+
ORDER BY
|
|
379
|
+
COALESCE(last_used_at, '') DESC,
|
|
380
|
+
created_at DESC,
|
|
381
|
+
id DESC
|
|
382
|
+
) AS rn
|
|
383
|
+
FROM api_keys
|
|
384
|
+
)
|
|
385
|
+
WHERE rn > 1
|
|
386
|
+
);
|
|
387
|
+
`);
|
|
388
|
+
db2.exec(`
|
|
389
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_api_keys_user_unique
|
|
390
|
+
ON api_keys(user_id);
|
|
391
|
+
`);
|
|
392
|
+
recordMigration("003_sessions_and_single_api_key");
|
|
393
|
+
})();
|
|
394
|
+
}
|
|
395
|
+
if (!hasMigration("004_license_cache_owner_email")) {
|
|
396
|
+
db2.transaction(() => {
|
|
397
|
+
const cols = db2.prepare("PRAGMA table_info(license_cache)").all().map((c) => c.name);
|
|
398
|
+
if (!cols.includes("owner_email")) {
|
|
399
|
+
db2.exec("ALTER TABLE license_cache ADD COLUMN owner_email TEXT");
|
|
400
|
+
}
|
|
401
|
+
recordMigration("004_license_cache_owner_email");
|
|
402
|
+
})();
|
|
403
|
+
}
|
|
329
404
|
}
|
|
330
405
|
|
|
331
406
|
// src/db/client.ts
|
|
332
407
|
var db = null;
|
|
333
408
|
function getDb() {
|
|
334
409
|
if (!db) {
|
|
335
|
-
mkdirSync(
|
|
410
|
+
mkdirSync(dirname2(config.DATABASE_PATH), { recursive: true });
|
|
336
411
|
db = new Database(config.DATABASE_PATH);
|
|
337
412
|
db.pragma("journal_mode = WAL");
|
|
338
413
|
db.pragma("foreign_keys = ON");
|
|
414
|
+
db.pragma("busy_timeout = 5000");
|
|
339
415
|
runMigrations(db);
|
|
340
416
|
}
|
|
341
417
|
return db;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getDb
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-2TW25QEA.js";
|
|
4
4
|
|
|
5
5
|
// src/governance/version-service.ts
|
|
6
6
|
import { createHash } from "crypto";
|
|
@@ -102,6 +102,28 @@ function getNodeTags(nestId, nodeId) {
|
|
|
102
102
|
return [];
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
+
function getDisplayStatus(nestId, nodeId) {
|
|
106
|
+
const db = getDb();
|
|
107
|
+
const pending = db.prepare(
|
|
108
|
+
"SELECT 1 FROM review_requests WHERE nest_id = ? AND node_id = ? AND status = 'pending' LIMIT 1"
|
|
109
|
+
).get(nestId, nodeId);
|
|
110
|
+
if (pending) return "pending_review";
|
|
111
|
+
const current = db.prepare(
|
|
112
|
+
`SELECT version, status FROM node_versions
|
|
113
|
+
WHERE nest_id = ? AND node_id = ?
|
|
114
|
+
ORDER BY version DESC LIMIT 1`
|
|
115
|
+
).get(nestId, nodeId);
|
|
116
|
+
if (!current) return "draft";
|
|
117
|
+
if (current.status === "approved") {
|
|
118
|
+
const approved = db.prepare(
|
|
119
|
+
"SELECT approved_version FROM approved_versions WHERE nest_id = ? AND node_id = ?"
|
|
120
|
+
).get(nestId, nodeId);
|
|
121
|
+
if (approved?.approved_version === current.version) return "approved";
|
|
122
|
+
return "draft";
|
|
123
|
+
}
|
|
124
|
+
if (current.status === "rejected") return "rejected";
|
|
125
|
+
return "draft";
|
|
126
|
+
}
|
|
105
127
|
function rowToVersion(row) {
|
|
106
128
|
return {
|
|
107
129
|
version: row.version,
|
|
@@ -123,5 +145,6 @@ export {
|
|
|
123
145
|
getApprovedVersion,
|
|
124
146
|
setApprovedVersion,
|
|
125
147
|
checkConflict,
|
|
126
|
-
getNodeTags
|
|
148
|
+
getNodeTags,
|
|
149
|
+
getDisplayStatus
|
|
127
150
|
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
canUserApprove,
|
|
3
3
|
resolveStewardsForNode
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-2FXVMVZJ.js";
|
|
5
5
|
import {
|
|
6
6
|
getDb
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-2TW25QEA.js";
|
|
8
8
|
|
|
9
9
|
// src/governance/review-service.ts
|
|
10
10
|
import { v4 as uuid } from "uuid";
|