@programinglive/dev-workflow-mcp-server 1.7.10 → 1.7.12
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/.dockerignore +60 -0
- package/.env.example +12 -0
- package/.env.production +16 -0
- package/CHANGELOG.md +45 -0
- package/Dockerfile +32 -0
- package/README.md +63 -2
- package/db/adapters/mysql.js +11 -4
- package/db/migrations/002_create_users_table.sql +22 -0
- package/db/migrations/003_create_workflow_history.sql +14 -0
- package/db/migrations/004_fix_workflow_history.sql +3 -0
- package/docker-compose.yml +24 -0
- package/docs/GCP_DEPLOYMENT.md +298 -0
- package/docs/product/PRD.md +5 -1
- package/docs/release-notes/RELEASE_NOTES.md +26 -0
- package/index.js +2 -0
- package/package.json +9 -3
- package/scripts/add-user.js +52 -0
- package/scripts/create-admin.js +47 -0
- package/scripts/deploy-gcloud.ps1 +160 -0
- package/scripts/deploy-gcloud.sh +154 -0
- package/tests/db-adapters.test.js +108 -0
- package/tests/env-support.test.js +46 -0
- package/tests/workflowState.test.js +16 -1
- package/web/__tests__/api/workflow-history.test.js +61 -0
- package/web/app/docs/page.tsx +1 -1
- package/web/app/history/page.tsx +213 -0
- package/web/app/layout.tsx +1 -1
- package/web/app/login/page.tsx +144 -0
- package/web/app/page.tsx +0 -2
- package/web/components/Hero.tsx +1 -1
- package/web/components/History.tsx +63 -16
- package/web/components/Navbar.tsx +111 -9
- package/web/lib/auth.js +75 -0
- package/web/middleware/auth.js +26 -0
- package/web/next.config.ts +4 -0
- package/web/package-lock.json +40 -40
- package/web/package.json +6 -5
- package/web/scripts/smoke-test.js +30 -0
- package/web/server.js +163 -18
package/web/server.js
CHANGED
|
@@ -1,31 +1,176 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { createServer } from 'http';
|
|
2
|
+
import { parse } from 'url';
|
|
3
|
+
import next from 'next';
|
|
4
|
+
import express from 'express';
|
|
5
|
+
import session from 'express-session';
|
|
6
|
+
import connectPgSimple from 'connect-pg-simple';
|
|
7
|
+
import pg from 'pg';
|
|
8
|
+
|
|
9
|
+
const pgSession = connectPgSimple(session);
|
|
10
|
+
const { Pool } = pg;
|
|
4
11
|
|
|
5
12
|
const dev = process.env.NODE_ENV !== 'production';
|
|
6
13
|
const hostname = 'localhost';
|
|
7
14
|
const port = process.env.PORT || 3000;
|
|
8
15
|
|
|
16
|
+
// Initialize PostgreSQL pool for sessions
|
|
17
|
+
const dbUrl = process.env.DEV_WORKFLOW_DB_URL;
|
|
18
|
+
const pool = dbUrl ? new Pool({ connectionString: dbUrl }) : null;
|
|
19
|
+
|
|
9
20
|
// Initialize Next.js app
|
|
10
21
|
const app = next({ dev, hostname, port });
|
|
11
22
|
const handle = app.getRequestHandler();
|
|
12
23
|
|
|
24
|
+
// Import auth utilities
|
|
25
|
+
let authenticateUser, getUserById;
|
|
26
|
+
if (pool) {
|
|
27
|
+
import('./lib/auth.js').then(module => {
|
|
28
|
+
authenticateUser = module.authenticateUser;
|
|
29
|
+
getUserById = module.getUserById;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
13
33
|
app.prepare().then(() => {
|
|
14
|
-
|
|
34
|
+
const server = express();
|
|
35
|
+
|
|
36
|
+
server.use(express.json());
|
|
37
|
+
server.use(express.urlencoded({ extended: true }));
|
|
38
|
+
|
|
39
|
+
// Session middleware
|
|
40
|
+
if (pool) {
|
|
41
|
+
server.use(
|
|
42
|
+
session({
|
|
43
|
+
store: new pgSession({
|
|
44
|
+
pool,
|
|
45
|
+
tableName: 'session'
|
|
46
|
+
}),
|
|
47
|
+
secret: process.env.SESSION_SECRET || 'dev-workflow-secret-change-in-production',
|
|
48
|
+
resave: false,
|
|
49
|
+
saveUninitialized: false,
|
|
50
|
+
cookie: {
|
|
51
|
+
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
|
|
52
|
+
httpOnly: true,
|
|
53
|
+
secure: process.env.NODE_ENV === 'production'
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Auth API routes
|
|
60
|
+
server.post('/api/auth/login', async (req, res) => {
|
|
61
|
+
if (!pool || !authenticateUser) {
|
|
62
|
+
return res.status(500).json({ error: 'Database not configured' });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const { username, password } = req.body;
|
|
66
|
+
|
|
67
|
+
if (!username || !password) {
|
|
68
|
+
return res.status(400).json({ error: 'Username and password required' });
|
|
69
|
+
}
|
|
70
|
+
|
|
15
71
|
try {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
72
|
+
const user = await authenticateUser(pool, username, password);
|
|
73
|
+
|
|
74
|
+
if (!user) {
|
|
75
|
+
return res.status(401).json({ error: 'Invalid credentials' });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
req.session.userId = user.id;
|
|
79
|
+
req.session.username = user.username;
|
|
80
|
+
|
|
81
|
+
res.json({ success: true, user: { username: user.username, email: user.email } });
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error('Login error:', error);
|
|
84
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
server.post('/api/auth/logout', (req, res) => {
|
|
89
|
+
req.session.destroy((err) => {
|
|
90
|
+
if (err) {
|
|
91
|
+
return res.status(500).json({ error: 'Logout failed' });
|
|
92
|
+
}
|
|
93
|
+
res.json({ success: true });
|
|
30
94
|
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
server.get('/api/auth/me', async (req, res) => {
|
|
98
|
+
if (!req.session || !req.session.userId) {
|
|
99
|
+
return res.status(401).json({ error: 'Not authenticated' });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!pool || !getUserById) {
|
|
103
|
+
return res.status(500).json({ error: 'Database not configured' });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const user = await getUserById(pool, req.session.userId);
|
|
108
|
+
|
|
109
|
+
if (!user) {
|
|
110
|
+
return res.status(404).json({ error: 'User not found' });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
res.json({ user: { username: user.username, email: user.email } });
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error('Get user error:', error);
|
|
116
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Get workflow history
|
|
121
|
+
server.get('/api/workflow/history', async (req, res) => {
|
|
122
|
+
if (!req.session || !req.session.userId) {
|
|
123
|
+
return res.status(401).json({ error: 'Not authenticated' });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!pool) {
|
|
127
|
+
return res.status(500).json({ error: 'Database not configured' });
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
// Get user details to filter history
|
|
132
|
+
// We assume workflow_history.user_id stores the username (based on schema)
|
|
133
|
+
const user = req.session.username; // This should be populated during login
|
|
134
|
+
|
|
135
|
+
let query = `
|
|
136
|
+
SELECT
|
|
137
|
+
id,
|
|
138
|
+
task_type,
|
|
139
|
+
task_description as description,
|
|
140
|
+
commit_message,
|
|
141
|
+
timestamp as completed_at,
|
|
142
|
+
tests_passed,
|
|
143
|
+
documentation_type
|
|
144
|
+
FROM workflow_history
|
|
145
|
+
`;
|
|
146
|
+
|
|
147
|
+
const params = [];
|
|
148
|
+
|
|
149
|
+
// If we have a username, filter by it.
|
|
150
|
+
// Note: DB schema has user_id just as text, assuming it matches username
|
|
151
|
+
if (user) {
|
|
152
|
+
query += ` WHERE user_id = $1 `;
|
|
153
|
+
params.push(user);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
query += ` ORDER BY timestamp DESC LIMIT 100`;
|
|
157
|
+
|
|
158
|
+
const result = await pool.query(query, params);
|
|
159
|
+
|
|
160
|
+
res.json({ history: result.rows });
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error('Get history error:', error);
|
|
163
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Next.js handler for all other routes
|
|
168
|
+
server.all('*', (req, res) => {
|
|
169
|
+
return handle(req, res);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
server.listen(port, (err) => {
|
|
173
|
+
if (err) throw err;
|
|
174
|
+
console.log(`> Ready on http://${hostname}:${port}`);
|
|
175
|
+
});
|
|
31
176
|
});
|