@programinglive/dev-workflow-mcp-server 1.7.12 → 1.7.14
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/CHANGELOG.md +48 -0
- package/cloudflared-config.yml +3 -0
- package/docker-compose.yml +19 -2
- package/docs/release-notes/RELEASE_NOTES.md +55 -0
- package/netlify.toml +7 -0
- package/nginx-api.conf +27 -0
- package/package.json +1 -1
- package/scripts/test-auth-flow.js +75 -0
- package/web/Dockerfile.api +25 -0
- package/web/api-server.js +211 -0
- package/web/app/history/page.tsx +25 -18
- package/web/app/login/page.tsx +12 -14
- package/web/components/Hero.tsx +1 -1
- package/web/components/History.tsx +4 -4
- package/web/components/Navbar.tsx +11 -12
- package/web/lib/api-client.ts +123 -0
- package/web/next.config.ts +2 -0
- package/web/package-lock.json +1033 -51
- package/web/package.json +13 -2
- package/web/scripts/db-query-history.js +120 -0
- package/web/server.js +13 -5
- package/scripts/add-user.js +0 -52
package/web/package.json
CHANGED
|
@@ -1,25 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "web",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.7.18",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "node server.js",
|
|
8
8
|
"build": "next build",
|
|
9
|
-
"start": "NODE_ENV=production node server.js",
|
|
9
|
+
"start": "cross-env NODE_ENV=production node server.js",
|
|
10
10
|
"lint": "next lint"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
+
"bcrypt": "^6.0.0",
|
|
14
|
+
"connect-pg-simple": "^10.0.0",
|
|
15
|
+
"cors": "^2.8.5",
|
|
16
|
+
"dotenv": "^17.2.3",
|
|
17
|
+
"express": "^5.2.1",
|
|
18
|
+
"express-session": "^1.18.2",
|
|
19
|
+
"jsonwebtoken": "^9.0.3",
|
|
13
20
|
"lucide-react": "^0.555.0",
|
|
14
21
|
"next": "^16.0.10",
|
|
22
|
+
"pg": "^8.16.3",
|
|
15
23
|
"react": "19.2.0",
|
|
16
24
|
"react-dom": "19.2.0"
|
|
17
25
|
},
|
|
18
26
|
"devDependencies": {
|
|
27
|
+
"@netlify/plugin-nextjs": "^5.15.2",
|
|
19
28
|
"@types/node": "^20",
|
|
29
|
+
"@types/pg": "^8.16.0",
|
|
20
30
|
"@types/react": "^19",
|
|
21
31
|
"@types/react-dom": "^19",
|
|
22
32
|
"autoprefixer": "^10.4.22",
|
|
33
|
+
"cross-env": "^10.1.0",
|
|
23
34
|
"eslint": "^9",
|
|
24
35
|
"eslint-config-next": "16.0.4",
|
|
25
36
|
"postcss": "^8.5.6",
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import pg from 'pg';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import dotenv from 'dotenv';
|
|
6
|
+
import { createRequire } from 'module';
|
|
7
|
+
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
const { Pool } = pg;
|
|
10
|
+
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = path.dirname(__filename);
|
|
13
|
+
|
|
14
|
+
// Silence stdout during dotenv loading to prevent noise in API output
|
|
15
|
+
const originalWrite = process.stdout.write;
|
|
16
|
+
process.stdout.write = () => true; // No-op
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// Try web/.env
|
|
20
|
+
dotenv.config({ path: path.join(__dirname, '../.env') });
|
|
21
|
+
// Try root .env
|
|
22
|
+
dotenv.config({ path: path.join(__dirname, '../../.env') });
|
|
23
|
+
} catch (e) {
|
|
24
|
+
// dotenv might not be available
|
|
25
|
+
} finally {
|
|
26
|
+
// Restore stdout
|
|
27
|
+
process.stdout.write = originalWrite;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const defaultUrl = 'postgres://devworkflow:devworkflow_secure_password_change_me@localhost:5432/devworkflow';
|
|
31
|
+
const connectionString = process.env.DEV_WORKFLOW_DB_URL || defaultUrl;
|
|
32
|
+
const rawSid = process.argv[2]; // Passed from API
|
|
33
|
+
|
|
34
|
+
// DEBUG: output connection details to stderr (so it doesn't break JSON on stdout)
|
|
35
|
+
try {
|
|
36
|
+
const urlStr = connectionString.startsWith('postgres') ? connectionString : `postgres://${connectionString}`; // handle simpler strings if needed
|
|
37
|
+
// Simple heuristic parsing or URL object
|
|
38
|
+
const u = new URL(urlStr);
|
|
39
|
+
console.error(`DEBUG: Connecting to Host: ${u.hostname}, Port: ${u.port}, User: ${u.username}`);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.error('DEBUG: Could not parse connection string for logging');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!connectionString) {
|
|
45
|
+
// Should be unreachable with defaultUrl, but good for safety
|
|
46
|
+
console.error(JSON.stringify({ error: 'Database configuration missing' }));
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const pool = new Pool({ connectionString });
|
|
51
|
+
|
|
52
|
+
async function run() {
|
|
53
|
+
try {
|
|
54
|
+
let userId = null;
|
|
55
|
+
|
|
56
|
+
// 1. Validate Session if provided
|
|
57
|
+
if (rawSid) {
|
|
58
|
+
// Parse 's:session_id.signature' -> 'session_id'
|
|
59
|
+
// Simplified logic: between 's:' and '.'
|
|
60
|
+
let sid = rawSid;
|
|
61
|
+
if (rawSid.startsWith('s:')) {
|
|
62
|
+
sid = rawSid.slice(2, rawSid.lastIndexOf('.'));
|
|
63
|
+
} else if (rawSid.includes('.')) {
|
|
64
|
+
// heuristic for signed cookie without s: prefix?
|
|
65
|
+
sid = rawSid.slice(0, rawSid.lastIndexOf('.'));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Try lookup
|
|
69
|
+
const sessionRes = await pool.query("SELECT sess FROM session WHERE sid = $1", [sid]);
|
|
70
|
+
if (sessionRes.rows.length > 0) {
|
|
71
|
+
const sessionData = sessionRes.rows[0].sess;
|
|
72
|
+
if (sessionData && sessionData.userId) {
|
|
73
|
+
userId = sessionData.userId;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 2. Fallback if no valid session found (for dev/recovery comfort)
|
|
79
|
+
if (!userId) {
|
|
80
|
+
// Try admin
|
|
81
|
+
const userRes = await pool.query("SELECT id FROM users WHERE LOWER(username) = 'admin'");
|
|
82
|
+
if (userRes.rows.length > 0) {
|
|
83
|
+
userId = userRes.rows[0].id;
|
|
84
|
+
} else {
|
|
85
|
+
const anyUser = await pool.query("SELECT id FROM users LIMIT 1");
|
|
86
|
+
if (anyUser.rows.length > 0) {
|
|
87
|
+
userId = anyUser.rows[0].id;
|
|
88
|
+
} else {
|
|
89
|
+
throw new Error('No user found');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 3. (Self-healing removed as it was causing schema errors)
|
|
95
|
+
|
|
96
|
+
const result = await pool.query(`
|
|
97
|
+
SELECT
|
|
98
|
+
id,
|
|
99
|
+
task_type,
|
|
100
|
+
description,
|
|
101
|
+
commit_message,
|
|
102
|
+
completed_at,
|
|
103
|
+
tests_passed,
|
|
104
|
+
documentation_type
|
|
105
|
+
FROM workflow_history
|
|
106
|
+
WHERE user_id = $1
|
|
107
|
+
ORDER BY completed_at DESC LIMIT 100
|
|
108
|
+
`, [userId]);
|
|
109
|
+
|
|
110
|
+
console.log(JSON.stringify({ history: result.rows }));
|
|
111
|
+
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error(JSON.stringify({ error: error.message, stack: error.stack }));
|
|
114
|
+
process.exit(1);
|
|
115
|
+
} finally {
|
|
116
|
+
await pool.end();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
run();
|
package/web/server.js
CHANGED
|
@@ -5,6 +5,15 @@ import express from 'express';
|
|
|
5
5
|
import session from 'express-session';
|
|
6
6
|
import connectPgSimple from 'connect-pg-simple';
|
|
7
7
|
import pg from 'pg';
|
|
8
|
+
import dotenv from 'dotenv';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = path.dirname(__filename);
|
|
14
|
+
|
|
15
|
+
// Load env from root
|
|
16
|
+
dotenv.config({ path: path.resolve(__dirname, '../.env') });
|
|
8
17
|
|
|
9
18
|
const pgSession = connectPgSimple(session);
|
|
10
19
|
const { Pool } = pg;
|
|
@@ -130,7 +139,8 @@ app.prepare().then(() => {
|
|
|
130
139
|
try {
|
|
131
140
|
// Get user details to filter history
|
|
132
141
|
// We assume workflow_history.user_id stores the username (based on schema)
|
|
133
|
-
|
|
142
|
+
// We use userId (integer) to query the foreign key column
|
|
143
|
+
const userId = req.session.userId;
|
|
134
144
|
|
|
135
145
|
let query = `
|
|
136
146
|
SELECT
|
|
@@ -146,11 +156,9 @@ app.prepare().then(() => {
|
|
|
146
156
|
|
|
147
157
|
const params = [];
|
|
148
158
|
|
|
149
|
-
|
|
150
|
-
// Note: DB schema has user_id just as text, assuming it matches username
|
|
151
|
-
if (user) {
|
|
159
|
+
if (userId) {
|
|
152
160
|
query += ` WHERE user_id = $1 `;
|
|
153
|
-
params.push(
|
|
161
|
+
params.push(userId);
|
|
154
162
|
}
|
|
155
163
|
|
|
156
164
|
query += ` ORDER BY timestamp DESC LIMIT 100`;
|
package/scripts/add-user.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import 'dotenv/config';
|
|
3
|
-
import pg from 'pg';
|
|
4
|
-
import { createUser } from '../web/lib/auth.js';
|
|
5
|
-
|
|
6
|
-
const { Pool } = pg;
|
|
7
|
-
|
|
8
|
-
// Get database configuration from environment
|
|
9
|
-
const dbUrl = process.env.DEV_WORKFLOW_DB_URL;
|
|
10
|
-
|
|
11
|
-
if (!dbUrl) {
|
|
12
|
-
console.error('Error: DEV_WORKFLOW_DB_URL environment variable is not set');
|
|
13
|
-
process.exit(1);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const pool = new Pool({ connectionString: dbUrl });
|
|
17
|
-
|
|
18
|
-
async function addUser() {
|
|
19
|
-
const username = process.argv[2];
|
|
20
|
-
const password = process.argv[3];
|
|
21
|
-
const email = process.argv[4];
|
|
22
|
-
|
|
23
|
-
if (!username || !password) {
|
|
24
|
-
console.error('Usage: node scripts/add-user.js <username> <password> [email]');
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
// Check if user already exists
|
|
30
|
-
const existing = await pool.query('SELECT id FROM users WHERE username = $1', [username]);
|
|
31
|
-
|
|
32
|
-
if (existing.rows.length > 0) {
|
|
33
|
-
console.log(`✗ User '${username}' already exists`);
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Create user
|
|
38
|
-
const user = await createUser(pool, username, password, email);
|
|
39
|
-
console.log(`✓ Created user: ${user.username}`);
|
|
40
|
-
if (user.email) {
|
|
41
|
-
console.log(` Email: ${user.email}`);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
} catch (error) {
|
|
45
|
-
console.error('Error creating user:', error.message);
|
|
46
|
-
process.exit(1);
|
|
47
|
-
} finally {
|
|
48
|
-
await pool.end();
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
addUser();
|